From 2c974c135d10b527d1beaa88d9cb3956612b7931 Mon Sep 17 00:00:00 2001 From: Anton Gladky Date: Tue, 12 Jan 2021 23:21:26 +0100 Subject: [PATCH] New upstream version 3.0.rc1+repack1 --- .clang-format | 41 + .travis.yml | 133 +- CHANGELOG.md | 144 + CMakeLists.txt | 452 +- CONTRIBUTING.md | 291 + README.md | 201 +- THIRD_PARTIES.txt | 50 + appveyor.yml | 28 - bench/CMakeLists.txt | 17 + bench/harness.cpp | 78 + cmake/AddVendoredSubdirectory.cmake | 10 + cmake/DisableWarnings.cmake | 15 + cmake/FindSpaceWare.cmake | 2 +- cmake/FindVendoredPackage.cmake | 56 + cmake/MacOSXBundleInfo.plist.in | 8 +- cmake/Toolchain-mingw32.cmake | 14 +- cmake/Toolchain-mingw64.cmake | 14 +- developer_docs/Solver_Transforms.txt | 154 + exposed/CDemo.c | 13 +- exposed/DOC.txt | 8 +- exposed/VbDemo.vb | 2 +- extlib/libdxfrw/drw_base.h | 31 +- extlib/libdxfrw/drw_entities.cpp | 33 +- extlib/libdxfrw/drw_entities.h | 8 +- extlib/libdxfrw/drw_header.cpp | 24 +- extlib/libdxfrw/drw_objects.cpp | 4 +- extlib/libdxfrw/drw_objects.h | 2 +- extlib/libdxfrw/intern/drw_cptable932.h | 2 +- extlib/libdxfrw/intern/drw_cptable936.h | 2 +- extlib/libdxfrw/intern/drw_cptable949.h | 2 +- extlib/libdxfrw/intern/drw_cptable950.h | 2 +- extlib/libdxfrw/intern/drw_cptables.h | 2 +- extlib/libdxfrw/intern/drw_textcodec.cpp | 43 +- extlib/libdxfrw/intern/drw_textcodec.h | 4 +- extlib/libdxfrw/intern/dwgbuffer.cpp | 20 +- extlib/libdxfrw/intern/dwgreader.h | 28 +- extlib/libdxfrw/intern/dwgreader18.cpp | 16 +- extlib/libdxfrw/intern/dwgreader21.cpp | 10 +- extlib/libdxfrw/intern/dwgutil.cpp | 12 +- extlib/libdxfrw/intern/dxfreader.h | 2 +- extlib/libdxfrw/libdxfrw.cpp | 42 +- extlib/mimalloc/.gitattributes | 10 + extlib/mimalloc/.gitignore | 8 + extlib/mimalloc/CMakeLists.txt | 361 + extlib/mimalloc/LICENSE | 21 + extlib/mimalloc/azure-pipelines.yml | 131 + extlib/mimalloc/bin/mimalloc-redirect.dll | Bin 0 -> 55808 bytes extlib/mimalloc/bin/mimalloc-redirect.lib | Bin 0 -> 2874 bytes extlib/mimalloc/bin/mimalloc-redirect32.dll | Bin 0 -> 39424 bytes extlib/mimalloc/bin/mimalloc-redirect32.lib | Bin 0 -> 2928 bytes .../cmake/mimalloc-config-version.cmake | 16 + extlib/mimalloc/cmake/mimalloc-config.cmake | 2 + .../doc/bench-c5-18xlarge-2020-01-20-a.svg | 886 ++ .../doc/bench-c5-18xlarge-2020-01-20-b.svg | 1184 +++ .../bench-c5-18xlarge-2020-01-20-rss-a.svg | 756 ++ .../bench-c5-18xlarge-2020-01-20-rss-b.svg | 1027 ++ extlib/mimalloc/doc/bench-r5a-1.svg | 768 ++ .../doc/bench-r5a-12xlarge-2020-01-16-a.svg | 867 ++ .../doc/bench-r5a-12xlarge-2020-01-16-b.svg | 1156 +++ extlib/mimalloc/doc/bench-r5a-2.svg | 982 ++ extlib/mimalloc/doc/bench-r5a-rss-1.svg | 682 ++ extlib/mimalloc/doc/bench-r5a-rss-2.svg | 853 ++ extlib/mimalloc/doc/bench-spec-rss.svg | 713 ++ extlib/mimalloc/doc/bench-spec.svg | 713 ++ extlib/mimalloc/doc/bench-z4-1.svg | 890 ++ extlib/mimalloc/doc/bench-z4-2.svg | 1146 +++ extlib/mimalloc/doc/bench-z4-rss-1.svg | 796 ++ extlib/mimalloc/doc/bench-z4-rss-2.svg | 974 ++ extlib/mimalloc/doc/doxyfile | 2564 +++++ extlib/mimalloc/doc/mimalloc-doc.h | 1199 +++ extlib/mimalloc/doc/mimalloc-doxygen.css | 49 + extlib/mimalloc/doc/mimalloc-logo-100.png | Bin 0 -> 3532 bytes extlib/mimalloc/doc/mimalloc-logo.png | Bin 0 -> 73097 bytes extlib/mimalloc/doc/mimalloc-logo.svg | 161 + extlib/mimalloc/docs/annotated.html | 122 + extlib/mimalloc/docs/annotated_dup.js | 5 + extlib/mimalloc/docs/bc_s.png | Bin 0 -> 657 bytes extlib/mimalloc/docs/bdwn.png | Bin 0 -> 142 bytes extlib/mimalloc/docs/bench.html | 120 + extlib/mimalloc/docs/build.html | 130 + extlib/mimalloc/docs/classes.html | 125 + extlib/mimalloc/docs/closed.png | Bin 0 -> 133 bytes extlib/mimalloc/docs/doc.png | Bin 0 -> 858 bytes extlib/mimalloc/docs/doxygen.css | 1764 ++++ extlib/mimalloc/docs/doxygen.png | Bin 0 -> 3777 bytes extlib/mimalloc/docs/dynsections.js | 120 + extlib/mimalloc/docs/environment.html | 127 + extlib/mimalloc/docs/folderclosed.png | Bin 0 -> 617 bytes extlib/mimalloc/docs/folderopen.png | Bin 0 -> 687 bytes extlib/mimalloc/docs/functions.html | 129 + extlib/mimalloc/docs/functions_vars.html | 129 + extlib/mimalloc/docs/group__aligned.html | 447 + extlib/mimalloc/docs/group__aligned.js | 11 + extlib/mimalloc/docs/group__analysis.html | 385 + extlib/mimalloc/docs/group__analysis.js | 15 + ...group__analysis_structmi__heap__area__t.js | 8 + extlib/mimalloc/docs/group__cpp.html | 396 + extlib/mimalloc/docs/group__cpp.js | 11 + extlib/mimalloc/docs/group__extended.html | 884 ++ extlib/mimalloc/docs/group__extended.js | 26 + extlib/mimalloc/docs/group__heap.html | 1080 +++ extlib/mimalloc/docs/group__heap.js | 30 + extlib/mimalloc/docs/group__malloc.html | 644 ++ extlib/mimalloc/docs/group__malloc.js | 16 + extlib/mimalloc/docs/group__options.html | 406 + extlib/mimalloc/docs/group__options.js | 29 + extlib/mimalloc/docs/group__posix.html | 496 + extlib/mimalloc/docs/group__posix.js | 16 + extlib/mimalloc/docs/group__typed.html | 521 ++ extlib/mimalloc/docs/group__typed.js | 14 + extlib/mimalloc/docs/group__zeroinit.html | 597 ++ extlib/mimalloc/docs/group__zeroinit.js | 14 + extlib/mimalloc/docs/index.html | 146 + extlib/mimalloc/docs/jquery.js | 87 + .../mimalloc/docs/mimalloc-doc_8h_source.html | 255 + extlib/mimalloc/docs/mimalloc-doxygen.css | 49 + extlib/mimalloc/docs/mimalloc-logo.svg | 161 + extlib/mimalloc/docs/modules.html | 130 + extlib/mimalloc/docs/modules.js | 13 + extlib/mimalloc/docs/nav_f.png | Bin 0 -> 170 bytes extlib/mimalloc/docs/nav_g.png | Bin 0 -> 95 bytes extlib/mimalloc/docs/nav_h.png | Bin 0 -> 98 bytes extlib/mimalloc/docs/navtree.css | 146 + extlib/mimalloc/docs/navtree.js | 540 ++ extlib/mimalloc/docs/navtreedata.js | 50 + extlib/mimalloc/docs/navtreeindex0.js | 175 + extlib/mimalloc/docs/open.png | Bin 0 -> 117 bytes extlib/mimalloc/docs/overrides.html | 142 + extlib/mimalloc/docs/pages.html | 125 + extlib/mimalloc/docs/resize.js | 136 + extlib/mimalloc/docs/search/all_0.html | 30 + extlib/mimalloc/docs/search/all_0.js | 4 + extlib/mimalloc/docs/search/all_1.html | 30 + extlib/mimalloc/docs/search/all_1.js | 4 + extlib/mimalloc/docs/search/all_2.html | 30 + extlib/mimalloc/docs/search/all_2.js | 7 + extlib/mimalloc/docs/search/all_3.html | 30 + extlib/mimalloc/docs/search/all_3.js | 5 + extlib/mimalloc/docs/search/all_4.html | 30 + extlib/mimalloc/docs/search/all_4.js | 5 + extlib/mimalloc/docs/search/all_5.html | 30 + extlib/mimalloc/docs/search/all_5.js | 5 + extlib/mimalloc/docs/search/all_6.html | 30 + extlib/mimalloc/docs/search/all_6.js | 147 + extlib/mimalloc/docs/search/all_7.html | 30 + extlib/mimalloc/docs/search/all_7.js | 4 + extlib/mimalloc/docs/search/all_8.html | 30 + extlib/mimalloc/docs/search/all_8.js | 5 + extlib/mimalloc/docs/search/all_9.html | 30 + extlib/mimalloc/docs/search/all_9.js | 5 + extlib/mimalloc/docs/search/all_a.html | 30 + extlib/mimalloc/docs/search/all_a.js | 4 + extlib/mimalloc/docs/search/all_b.html | 30 + extlib/mimalloc/docs/search/all_b.js | 5 + extlib/mimalloc/docs/search/all_c.html | 30 + extlib/mimalloc/docs/search/all_c.js | 4 + extlib/mimalloc/docs/search/all_d.html | 30 + extlib/mimalloc/docs/search/all_d.js | 4 + extlib/mimalloc/docs/search/classes_0.html | 30 + extlib/mimalloc/docs/search/classes_0.js | 5 + extlib/mimalloc/docs/search/close.png | Bin 0 -> 273 bytes extlib/mimalloc/docs/search/enums_0.html | 30 + extlib/mimalloc/docs/search/enums_0.js | 4 + extlib/mimalloc/docs/search/enumvalues_0.html | 30 + extlib/mimalloc/docs/search/enumvalues_0.js | 4 + extlib/mimalloc/docs/search/enumvalues_1.html | 30 + extlib/mimalloc/docs/search/enumvalues_1.js | 18 + extlib/mimalloc/docs/search/functions_0.html | 30 + extlib/mimalloc/docs/search/functions_0.js | 112 + extlib/mimalloc/docs/search/functions_1.html | 30 + extlib/mimalloc/docs/search/functions_1.js | 4 + extlib/mimalloc/docs/search/groups_0.html | 30 + extlib/mimalloc/docs/search/groups_0.js | 4 + extlib/mimalloc/docs/search/groups_1.html | 30 + extlib/mimalloc/docs/search/groups_1.js | 4 + extlib/mimalloc/docs/search/groups_2.html | 30 + extlib/mimalloc/docs/search/groups_2.js | 4 + extlib/mimalloc/docs/search/groups_3.html | 30 + extlib/mimalloc/docs/search/groups_3.js | 4 + extlib/mimalloc/docs/search/groups_4.html | 30 + extlib/mimalloc/docs/search/groups_4.js | 5 + extlib/mimalloc/docs/search/groups_5.html | 30 + extlib/mimalloc/docs/search/groups_5.js | 4 + extlib/mimalloc/docs/search/groups_6.html | 30 + extlib/mimalloc/docs/search/groups_6.js | 4 + extlib/mimalloc/docs/search/groups_7.html | 30 + extlib/mimalloc/docs/search/groups_7.js | 4 + extlib/mimalloc/docs/search/groups_8.html | 30 + extlib/mimalloc/docs/search/groups_8.js | 4 + extlib/mimalloc/docs/search/mag_sel.png | Bin 0 -> 465 bytes extlib/mimalloc/docs/search/nomatches.html | 12 + extlib/mimalloc/docs/search/pages_0.html | 30 + extlib/mimalloc/docs/search/pages_0.js | 4 + extlib/mimalloc/docs/search/pages_1.html | 30 + extlib/mimalloc/docs/search/pages_1.js | 4 + extlib/mimalloc/docs/search/pages_2.html | 30 + extlib/mimalloc/docs/search/pages_2.js | 4 + extlib/mimalloc/docs/search/pages_3.html | 30 + extlib/mimalloc/docs/search/pages_3.js | 4 + extlib/mimalloc/docs/search/pages_4.html | 30 + extlib/mimalloc/docs/search/pages_4.js | 4 + extlib/mimalloc/docs/search/search.css | 273 + extlib/mimalloc/docs/search/search.js | 814 ++ extlib/mimalloc/docs/search/search_l.png | Bin 0 -> 567 bytes extlib/mimalloc/docs/search/search_m.png | Bin 0 -> 158 bytes extlib/mimalloc/docs/search/search_r.png | Bin 0 -> 553 bytes extlib/mimalloc/docs/search/searchdata.js | 39 + extlib/mimalloc/docs/search/typedefs_0.html | 30 + extlib/mimalloc/docs/search/typedefs_0.js | 8 + extlib/mimalloc/docs/search/typedefs_1.html | 30 + extlib/mimalloc/docs/search/typedefs_1.js | 4 + extlib/mimalloc/docs/search/typedefs_2.html | 30 + extlib/mimalloc/docs/search/typedefs_2.js | 5 + extlib/mimalloc/docs/search/variables_0.html | 30 + extlib/mimalloc/docs/search/variables_0.js | 5 + extlib/mimalloc/docs/search/variables_1.html | 30 + extlib/mimalloc/docs/search/variables_1.js | 4 + extlib/mimalloc/docs/search/variables_2.html | 30 + extlib/mimalloc/docs/search/variables_2.js | 4 + extlib/mimalloc/docs/search/variables_3.html | 30 + extlib/mimalloc/docs/search/variables_3.js | 4 + extlib/mimalloc/docs/splitbar.png | Bin 0 -> 304 bytes extlib/mimalloc/docs/sync_off.png | Bin 0 -> 851 bytes extlib/mimalloc/docs/sync_on.png | Bin 0 -> 836 bytes extlib/mimalloc/docs/tab_a.png | Bin 0 -> 120 bytes extlib/mimalloc/docs/tab_b.png | Bin 0 -> 180 bytes extlib/mimalloc/docs/tab_h.png | Bin 0 -> 174 bytes extlib/mimalloc/docs/tab_s.png | Bin 0 -> 178 bytes extlib/mimalloc/docs/tabs.css | 61 + extlib/mimalloc/docs/using.html | 124 + .../ide/vs2017/mimalloc-override-test.vcxproj | 190 + .../mimalloc-override-test.vcxproj.filters | 22 + .../ide/vs2017/mimalloc-override.vcxproj | 254 + .../vs2017/mimalloc-override.vcxproj.filters | 80 + .../ide/vs2017/mimalloc-test-stress.vcxproj | 159 + .../mimalloc-test-stress.vcxproj.filters | 22 + .../mimalloc/ide/vs2017/mimalloc-test.vcxproj | 158 + .../ide/vs2017/mimalloc-test.vcxproj.filters | 22 + extlib/mimalloc/ide/vs2017/mimalloc.sln | 71 + extlib/mimalloc/ide/vs2017/mimalloc.vcxproj | 260 + .../ide/vs2017/mimalloc.vcxproj.filters | 83 + .../ide/vs2019/mimalloc-override-test.vcxproj | 190 + .../ide/vs2019/mimalloc-override.vcxproj | 257 + .../vs2019/mimalloc-override.vcxproj.filters | 81 + .../ide/vs2019/mimalloc-test-api.vcxproj | 155 + .../ide/vs2019/mimalloc-test-stress.vcxproj | 159 + .../mimalloc/ide/vs2019/mimalloc-test.vcxproj | 158 + extlib/mimalloc/ide/vs2019/mimalloc.sln | 81 + extlib/mimalloc/ide/vs2019/mimalloc.vcxproj | 253 + .../ide/vs2019/mimalloc.vcxproj.filters | 84 + extlib/mimalloc/include/mimalloc-atomic.h | 294 + extlib/mimalloc/include/mimalloc-internal.h | 748 ++ extlib/mimalloc/include/mimalloc-new-delete.h | 52 + extlib/mimalloc/include/mimalloc-override.h | 66 + extlib/mimalloc/include/mimalloc-types.h | 480 + extlib/mimalloc/include/mimalloc.h | 426 + extlib/mimalloc/readme.md | 583 ++ extlib/mimalloc/src/alloc-aligned.c | 205 + extlib/mimalloc/src/alloc-override-osx.c | 260 + extlib/mimalloc/src/alloc-override.c | 214 + extlib/mimalloc/src/alloc-posix.c | 155 + extlib/mimalloc/src/alloc.c | 857 ++ extlib/mimalloc/src/arena.c | 357 + extlib/mimalloc/src/bitmap.inc.c | 240 + extlib/mimalloc/src/heap.c | 555 ++ extlib/mimalloc/src/init.c | 572 ++ extlib/mimalloc/src/options.c | 518 + extlib/mimalloc/src/os.c | 1176 +++ extlib/mimalloc/src/page-queue.c | 368 + extlib/mimalloc/src/page.c | 847 ++ extlib/mimalloc/src/random.c | 328 + extlib/mimalloc/src/region.c | 498 + extlib/mimalloc/src/segment.c | 1343 +++ extlib/mimalloc/src/static.c | 38 + extlib/mimalloc/src/stats.c | 532 ++ extlib/mimalloc/test/CMakeLists.txt | 46 + extlib/mimalloc/test/main-override-static.c | 123 + extlib/mimalloc/test/main-override.c | 30 + extlib/mimalloc/test/main-override.cpp | 209 + extlib/mimalloc/test/main.c | 46 + extlib/mimalloc/test/readme.md | 16 + extlib/mimalloc/test/test-api.c | 249 + extlib/mimalloc/test/test-stress.c | 329 + pkg/flatpak/.gitignore | 4 + pkg/flatpak/build.sh | 4 + pkg/flatpak/com.solvespace.SolveSpace.json | 141 + pkg/snap/.gitignore | 3 + pkg/snap/build.sh | 12 + pkg/snap/snap/snapcraft.yaml | 80 + res/CMakeLists.txt | 294 + res/banner.txt | 1 + .../cocoa/AppIcon.iconset/icon_128x128.png | Bin .../cocoa/AppIcon.iconset/icon_128x128@2x.png | Bin .../cocoa/AppIcon.iconset/icon_16x16.png | Bin .../cocoa/AppIcon.iconset/icon_16x16@2x.png | Bin .../cocoa/AppIcon.iconset/icon_256x256.png | Bin .../cocoa/AppIcon.iconset/icon_256x256@2x.png | Bin .../cocoa/AppIcon.iconset/icon_32x32.png | Bin .../cocoa/AppIcon.iconset/icon_32x32@2x.png | Bin .../cocoa/AppIcon.iconset/icon_512x512.png | Bin .../cocoa/AppIcon.iconset/icon_512x512@2x.png | Bin {src => res}/cocoa/MainMenu.xib | 2 +- {src => res}/cocoa/SaveFormatAccessory.xib | 1 + res/fonts/BitstreamVeraSans-Roman-builtin.ttf | Bin 0 -> 54524 bytes {src => res}/fonts/private/0-check-false.png | Bin {src => res}/fonts/private/1-check-true.png | Bin {src => res}/fonts/private/2-radio-false.png | Bin {src => res}/fonts/private/3-radio-true.png | Bin {src => res}/fonts/private/4-stipple-dot.png | Bin .../fonts/private/5-stipple-dash-long.png | Bin {src => res}/fonts/private/6-stipple-dash.png | Bin .../fonts/private/7-stipple-zigzag.png | Bin {src => res}/fonts/unicode.lff.gz | Bin res/fonts/unifont.hex.gz | Bin 0 -> 959159 bytes .../freedesktop}/solvespace-16x16.png | Bin .../freedesktop}/solvespace-16x16.xpm | 0 .../freedesktop}/solvespace-24x24.png | Bin .../freedesktop}/solvespace-24x24.xpm | 0 .../freedesktop}/solvespace-32x32.png | Bin .../freedesktop}/solvespace-32x32.xpm | 0 .../freedesktop}/solvespace-48x48.png | Bin .../freedesktop}/solvespace-48x48.xpm | 0 res/freedesktop/solvespace-flatpak-mime.xml | 8 + res/freedesktop/solvespace-flatpak.desktop.in | 10 + res/freedesktop/solvespace-mime.xml | 8 + res/freedesktop/solvespace-scalable.svg | 12 + res/freedesktop/solvespace-snap.desktop | 10 + .../freedesktop/solvespace.desktop.in | 3 +- res/icons/graphics-window/angle.png | Bin 0 -> 819 bytes res/icons/graphics-window/arc.png | Bin 0 -> 686 bytes res/icons/graphics-window/assemble.png | Bin 0 -> 454 bytes res/icons/graphics-window/bezier.png | Bin 0 -> 710 bytes res/icons/graphics-window/circle.png | Bin 0 -> 801 bytes res/icons/graphics-window/construction.png | Bin 0 -> 739 bytes res/icons/graphics-window/equal.png | Bin 0 -> 920 bytes res/icons/graphics-window/extrude.png | Bin 0 -> 620 bytes res/icons/graphics-window/horiz.png | Bin 0 -> 418 bytes res/icons/graphics-window/image.png | Bin 0 -> 1025 bytes res/icons/graphics-window/in3d.png | Bin 0 -> 512 bytes res/icons/graphics-window/lathe.png | Bin 0 -> 401 bytes res/icons/graphics-window/length.png | Bin 0 -> 480 bytes res/icons/graphics-window/line.png | Bin 0 -> 511 bytes res/icons/graphics-window/ontoworkplane.png | Bin 0 -> 412 bytes res/icons/graphics-window/other-supp.png | Bin 0 -> 916 bytes res/icons/graphics-window/parallel.png | Bin 0 -> 531 bytes res/icons/graphics-window/perpendicular.png | Bin 0 -> 427 bytes res/icons/graphics-window/point.png | Bin 0 -> 394 bytes res/icons/graphics-window/pointonx.png | Bin 0 -> 596 bytes res/icons/graphics-window/rectangle.png | Bin 0 -> 418 bytes res/icons/graphics-window/ref.png | Bin 0 -> 413 bytes .../graphics-window/same-orientation.png | Bin 0 -> 673 bytes res/icons/graphics-window/sketch-in-3d.png | Bin 0 -> 597 bytes res/icons/graphics-window/sketch-in-plane.png | Bin 0 -> 507 bytes res/icons/graphics-window/step-rotate.png | Bin 0 -> 871 bytes res/icons/graphics-window/step-translate.png | Bin 0 -> 411 bytes res/icons/graphics-window/symmetric.png | Bin 0 -> 515 bytes res/icons/graphics-window/tangent-arc.png | Bin 0 -> 666 bytes res/icons/graphics-window/text.png | Bin 0 -> 784 bytes res/icons/graphics-window/trim.png | Bin 0 -> 575 bytes res/icons/graphics-window/vert.png | Bin 0 -> 515 bytes res/icons/text-window/constraint.png | Bin 0 -> 557 bytes res/icons/text-window/construction.png | Bin 0 -> 739 bytes res/icons/text-window/edges.png | Bin 0 -> 703 bytes res/icons/text-window/faces.png | Bin 0 -> 683 bytes res/icons/text-window/mesh.png | Bin 0 -> 1169 bytes res/icons/text-window/normal.png | Bin 0 -> 639 bytes res/icons/text-window/occluded-invisible.png | Bin 0 -> 345 bytes res/icons/text-window/occluded-stippled.png | Bin 0 -> 539 bytes res/icons/text-window/occluded-visible.png | Bin 0 -> 365 bytes res/icons/text-window/outlines.png | Bin 0 -> 733 bytes res/icons/text-window/point.png | Bin 0 -> 394 bytes res/icons/text-window/shaded.png | Bin 0 -> 403 bytes res/icons/text-window/workplane.png | Bin 0 -> 462 bytes res/locales.txt | 8 + res/locales/de_DE.po | 2021 ++++ res/locales/en_US.po | 1877 ++++ res/locales/fr_FR.po | 2041 ++++ res/locales/ru_RU.po | 2026 ++++ res/locales/uk_UA.po | 1697 ++++ res/locales/zh_CN.po | 1778 ++++ res/messages.pot | 1653 ++++ res/shaders/edge.frag | 35 + res/shaders/edge.vert | 41 + res/shaders/imesh.frag | 12 + res/shaders/imesh.vert | 13 + res/shaders/imesh_point.frag | 15 + res/shaders/imesh_point.vert | 33 + res/shaders/imesh_tex.frag | 15 + res/shaders/imesh_tex.vert | 17 + res/shaders/imesh_texa.frag | 13 + res/shaders/mesh.frag | 26 + res/shaders/mesh.vert | 21 + res/shaders/mesh_fill.frag | 12 + res/shaders/mesh_fill.vert | 13 + res/shaders/outline.vert | 60 + res/threejs/SolveSpaceControls.js | 536 ++ res/threejs/hammer-2.0.8.js.gz | Bin 0 -> 17503 bytes res/threejs/three-r76.js.gz | Bin 0 -> 189743 bytes {src => res}/win32/icon.ico | Bin {src => res}/win32/manifest.xml | 12 +- res/win32/versioninfo.rc.in | 29 + src/CMakeLists.txt | 568 +- src/bsp.cpp | 687 +- src/clipboard.cpp | 173 +- src/cocoa/cocoamain.mm | 1351 --- src/config.h.in | 15 +- src/confscreen.cpp | 279 +- src/constraint.cpp | 531 +- src/constrainteq.cpp | 415 +- src/describescreen.cpp | 290 +- src/draw.cpp | 948 +- src/drawconstraint.cpp | 879 +- src/drawentity.cpp | 712 +- src/dsc.h | 574 +- src/entity.cpp | 647 +- src/export.cpp | 1140 +-- src/exportstep.cpp | 74 +- src/exportvector.cpp | 486 +- src/expr.cpp | 761 +- src/expr.h | 135 +- src/file.cpp | 685 +- src/generate.cpp | 324 +- src/glhelper.cpp | 921 -- src/graphicswin.cpp | 1129 ++- src/group.cpp | 788 +- src/groupmesh.cpp | 633 +- src/gtk/gtkmain.cpp | 1611 ---- src/icons/angle.png | Bin 27359 -> 0 bytes src/icons/arc.png | Bin 27478 -> 0 bytes src/icons/assemble.png | Bin 27011 -> 0 bytes src/icons/bezier.png | Bin 27089 -> 0 bytes src/icons/circle.png | Bin 27376 -> 0 bytes src/icons/constraint.png | Bin 26545 -> 0 bytes src/icons/construction.png | Bin 27069 -> 0 bytes src/icons/edges.png | Bin 27374 -> 0 bytes src/icons/equal.png | Bin 27427 -> 0 bytes src/icons/extrude.png | Bin 27030 -> 0 bytes src/icons/faces.png | Bin 609 -> 0 bytes src/icons/hidden-lines.png | Bin 544 -> 0 bytes src/icons/horiz.png | Bin 26274 -> 0 bytes src/icons/in3d.png | Bin 26608 -> 0 bytes src/icons/lathe.png | Bin 171 -> 0 bytes src/icons/length.png | Bin 26577 -> 0 bytes src/icons/line.png | Bin 26295 -> 0 bytes src/icons/mesh.png | Bin 28445 -> 0 bytes src/icons/normal.png | Bin 26211 -> 0 bytes src/icons/ontoworkplane.png | Bin 26007 -> 0 bytes src/icons/other-supp.png | Bin 27260 -> 0 bytes src/icons/outlines.png | Bin 930 -> 0 bytes src/icons/parallel.png | Bin 26455 -> 0 bytes src/icons/perpendicular.png | Bin 26400 -> 0 bytes src/icons/point.png | Bin 25742 -> 0 bytes src/icons/pointonx.png | Bin 26848 -> 0 bytes src/icons/rectangle.png | Bin 26335 -> 0 bytes src/icons/ref.png | Bin 25761 -> 0 bytes src/icons/same-orientation.png | Bin 26407 -> 0 bytes src/icons/shaded.png | Bin 317 -> 0 bytes src/icons/sketch-in-3d.png | Bin 26669 -> 0 bytes src/icons/sketch-in-plane.png | Bin 26671 -> 0 bytes src/icons/step-rotate.png | Bin 27352 -> 0 bytes src/icons/step-translate.png | Bin 26253 -> 0 bytes src/icons/symmetric.png | Bin 26180 -> 0 bytes src/icons/tangent-arc.png | Bin 27250 -> 0 bytes src/icons/text.png | Bin 26716 -> 0 bytes src/icons/trim.png | Bin 26748 -> 0 bytes src/icons/vert.png | Bin 26517 -> 0 bytes src/icons/workplane.png | Bin 395 -> 0 bytes src/importdxf.cpp | 515 +- src/importidf.cpp | 521 ++ src/lib.cpp | 145 +- src/mesh.cpp | 409 +- src/modify.cpp | 405 +- src/mouse.cpp | 1369 +-- src/platform/entrycli.cpp | 387 + src/platform/entrygui.cpp | 42 + src/platform/gui.cpp | 135 + src/platform/gui.h | 389 + src/platform/guigtk.cpp | 1493 +++ src/platform/guimac.mm | 1494 +++ src/platform/guinone.cpp | 145 + src/platform/guiwin.cpp | 1690 ++++ src/platform/platform.cpp | 713 ++ src/platform/platform.h | 79 + src/polygon.cpp | 378 +- src/polygon.h | 291 +- src/polyline.cpp | 243 + src/render/gl3shader.cpp | 1071 +++ src/render/gl3shader.h | 266 + src/render/render.cpp | 459 + src/render/render.h | 385 + src/render/render2d.cpp | 413 + src/render/rendercairo.cpp | 173 + src/render/rendergl1.cpp | 852 ++ src/render/rendergl3.cpp | 1102 +++ src/request.cpp | 172 +- src/resource.cpp | 1583 ++++ src/resource.h | 112 + src/sketch.h | 662 +- src/solvespace.cpp | 1091 ++- src/solvespace.h | 751 +- src/srf/boolean.cpp | 492 +- src/srf/curve.cpp | 250 +- src/srf/merge.cpp | 23 +- src/srf/ratpoly.cpp | 402 +- src/srf/raycast.cpp | 164 +- src/srf/surface.cpp | 487 +- src/srf/surface.h | 272 +- src/srf/surfinter.cpp | 140 +- src/srf/triangulate.cpp | 441 +- src/style.cpp | 412 +- src/system.cpp | 273 +- src/textscreens.cpp | 521 +- src/textwin.cpp | 915 +- src/toolbar.cpp | 319 +- src/ttf.cpp | 195 +- src/ttf.h | 17 +- src/ui.h | 801 +- src/undoredo.cpp | 74 +- src/unix/gloffscreen.cpp | 86 - src/unix/gloffscreen.h | 36 - src/unix/unixutil.cpp | 112 - src/util.cpp | 548 +- src/view.cpp | 40 +- src/win32/resource.rc | 6 - src/win32/w32main.cpp | 1479 --- src/win32/w32util.cpp | 138 - test/CMakeLists.txt | 138 + test/Gentium-R.ttf | Bin 0 -> 362644 bytes test/analysis/contour_area/normal.png | Bin 0 -> 4787 bytes test/analysis/contour_area/normal.slvs | 512 + test/analysis/contour_area/test.cpp | 7 + test/commit.sh | 9 + test/constraint/angle/free_in_3d.png | Bin 0 -> 4763 bytes test/constraint/angle/free_in_3d.slvs | 355 + test/constraint/angle/free_in_3d_v20.slvs | 355 + test/constraint/angle/free_in_3d_v22.slvs | 355 + test/constraint/angle/normal.png | Bin 0 -> 4882 bytes test/constraint/angle/normal.slvs | 352 + test/constraint/angle/normal_v20.slvs | 352 + test/constraint/angle/normal_v22.slvs | 352 + test/constraint/angle/reference.png | Bin 0 -> 5239 bytes test/constraint/angle/reference.slvs | 366 + .../constraint/angle/reference_free_in_3d.png | Bin 0 -> 4857 bytes .../angle/reference_free_in_3d.slvs | 355 + .../angle/reference_free_in_3d_v20.slvs | 355 + .../angle/reference_free_in_3d_v22.slvs | 355 + test/constraint/angle/reference_v20.slvs | 366 + test/constraint/angle/reference_v22.slvs | 366 + test/constraint/angle/skew.png | Bin 0 -> 4869 bytes test/constraint/angle/skew.slvs | 340 + test/constraint/angle/skew_v22.slvs | 340 + test/constraint/angle/test.cpp | 70 + test/constraint/arc_line_tangent/normal.png | Bin 0 -> 5100 bytes test/constraint/arc_line_tangent/normal.slvs | 450 + .../arc_line_tangent/normal_v20.slvs | 450 + .../arc_line_tangent/normal_v22.slvs | 450 + test/constraint/arc_line_tangent/test.cpp | 17 + .../at_midpoint/line_plane_free_in_3d.png | Bin 0 -> 4333 bytes .../at_midpoint/line_plane_free_in_3d.slvs | 290 + .../line_plane_free_in_3d_v20.slvs | 288 + .../line_plane_free_in_3d_v22.slvs | 290 + .../at_midpoint/line_plane_normal.png | Bin 0 -> 4333 bytes .../at_midpoint/line_plane_normal.slvs | 289 + .../at_midpoint/line_plane_normal_v20.slvs | 287 + .../at_midpoint/line_plane_normal_v22.slvs | 289 + .../at_midpoint/line_pt_free_in_3d.png | Bin 0 -> 4298 bytes .../at_midpoint/line_pt_free_in_3d.slvs | 315 + .../at_midpoint/line_pt_free_in_3d_v20.slvs | 313 + .../at_midpoint/line_pt_free_in_3d_v22.slvs | 315 + .../constraint/at_midpoint/line_pt_normal.png | Bin 0 -> 4298 bytes .../at_midpoint/line_pt_normal.slvs | 313 + .../at_midpoint/line_pt_normal_v20.slvs | 311 + .../at_midpoint/line_pt_normal_v22.slvs | 313 + test/constraint/at_midpoint/test.cpp | 65 + test/constraint/comment/normal.png | Bin 0 -> 4314 bytes test/constraint/comment/normal.slvs | 240 + test/constraint/comment/normal_v20.slvs | 238 + test/constraint/comment/normal_v22.slvs | 240 + test/constraint/comment/test.cpp | 17 + .../cubic_line_tangent/free_in_3d.png | Bin 0 -> 5073 bytes .../cubic_line_tangent/free_in_3d.slvs | 397 + .../cubic_line_tangent/free_in_3d_v20.slvs | 392 + .../cubic_line_tangent/free_in_3d_v22.slvs | 392 + test/constraint/cubic_line_tangent/normal.png | Bin 0 -> 5220 bytes .../constraint/cubic_line_tangent/normal.slvs | 446 + .../cubic_line_tangent/normal_v20.slvs | 446 + .../cubic_line_tangent/normal_v22.slvs | 446 + test/constraint/cubic_line_tangent/test.cpp | 33 + .../curve_curve_tangent/arc_arc.png | Bin 0 -> 5027 bytes .../curve_curve_tangent/arc_arc.slvs | 406 + .../curve_curve_tangent/arc_arc_v20.slvs | 404 + .../curve_curve_tangent/arc_arc_v22.slvs | 406 + .../curve_curve_tangent/arc_cubic.png | Bin 0 -> 5201 bytes .../curve_curve_tangent/arc_cubic.slvs | 506 + .../curve_curve_tangent/arc_cubic_v20.slvs | 506 + .../curve_curve_tangent/arc_cubic_v22.slvs | 506 + test/constraint/curve_curve_tangent/test.cpp | 33 + test/constraint/diameter/normal.png | Bin 0 -> 5024 bytes test/constraint/diameter/normal.slvs | 296 + test/constraint/diameter/normal_v20.slvs | 294 + test/constraint/diameter/normal_v22.slvs | 296 + test/constraint/diameter/reference.png | Bin 0 -> 5101 bytes test/constraint/diameter/reference.slvs | 296 + test/constraint/diameter/reference_v20.slvs | 296 + test/constraint/diameter/reference_v22.slvs | 296 + test/constraint/diameter/test.cpp | 33 + test/constraint/eq_len_pt_line_d/normal.png | Bin 0 -> 4407 bytes test/constraint/eq_len_pt_line_d/normal.slvs | 364 + .../eq_len_pt_line_d/normal_v20.slvs | 362 + .../eq_len_pt_line_d/normal_v22.slvs | 364 + test/constraint/eq_len_pt_line_d/test.cpp | 17 + test/constraint/eq_pt_ln_distances/normal.png | Bin 0 -> 4363 bytes .../constraint/eq_pt_ln_distances/normal.slvs | 389 + .../eq_pt_ln_distances/normal_v20.slvs | 387 + .../eq_pt_ln_distances/normal_v22.slvs | 389 + test/constraint/eq_pt_ln_distances/test.cpp | 17 + test/constraint/equal_angle/normal.png | Bin 0 -> 5267 bytes test/constraint/equal_angle/normal.slvs | 463 + test/constraint/equal_angle/normal_v20.slvs | 461 + test/constraint/equal_angle/normal_v22.slvs | 463 + test/constraint/equal_angle/other.png | Bin 0 -> 4914 bytes test/constraint/equal_angle/other.slvs | 463 + test/constraint/equal_angle/other_v20.slvs | 463 + test/constraint/equal_angle/other_v22.slvs | 463 + test/constraint/equal_angle/test.cpp | 33 + test/constraint/equal_length_lines/normal.png | Bin 0 -> 4341 bytes .../constraint/equal_length_lines/normal.slvs | 339 + .../equal_length_lines/normal_v20.slvs | 337 + .../equal_length_lines/normal_v22.slvs | 339 + test/constraint/equal_length_lines/test.cpp | 17 + test/constraint/equal_line_arc_len/normal.png | Bin 0 -> 4564 bytes .../constraint/equal_line_arc_len/normal.slvs | 367 + .../equal_line_arc_len/normal_v20.slvs | 367 + .../equal_line_arc_len/normal_v22.slvs | 367 + test/constraint/equal_line_arc_len/pi.png | Bin 0 -> 4667 bytes test/constraint/equal_line_arc_len/pi.slvs | 367 + test/constraint/equal_line_arc_len/tau.png | Bin 0 -> 4963 bytes test/constraint/equal_line_arc_len/tau.slvs | 367 + test/constraint/equal_line_arc_len/test.cpp | 27 + test/constraint/equal_radius/normal.png | Bin 0 -> 5258 bytes test/constraint/equal_radius/normal.slvs | 349 + test/constraint/equal_radius/normal_v20.slvs | 347 + test/constraint/equal_radius/normal_v22.slvs | 349 + test/constraint/equal_radius/test.cpp | 17 + test/constraint/horizontal/line.png | Bin 0 -> 4274 bytes test/constraint/horizontal/line.slvs | 288 + test/constraint/horizontal/line_v20.slvs | 286 + test/constraint/horizontal/line_v22.slvs | 288 + test/constraint/horizontal/pt_pt.png | Bin 0 -> 4268 bytes test/constraint/horizontal/pt_pt.slvs | 287 + test/constraint/horizontal/pt_pt_v20.slvs | 287 + test/constraint/horizontal/pt_pt_v22.slvs | 287 + test/constraint/horizontal/test.cpp | 33 + test/constraint/length_difference/normal.png | Bin 0 -> 4462 bytes test/constraint/length_difference/normal.slvs | 341 + .../length_difference/normal_v22.slvs | 341 + .../length_difference/reference.png | Bin 0 -> 4542 bytes .../length_difference/reference.slvs | 341 + .../length_difference/reference_v22.slvs | 341 + test/constraint/length_difference/test.cpp | 23 + test/constraint/length_ratio/normal.png | Bin 0 -> 4546 bytes test/constraint/length_ratio/normal.slvs | 342 + test/constraint/length_ratio/normal_v20.slvs | 340 + test/constraint/length_ratio/normal_v22.slvs | 342 + test/constraint/length_ratio/reference.png | Bin 0 -> 4616 bytes test/constraint/length_ratio/reference.slvs | 342 + .../length_ratio/reference_v20.slvs | 342 + .../length_ratio/reference_v22.slvs | 342 + test/constraint/length_ratio/test.cpp | 33 + test/constraint/parallel/free_in_3d.png | Bin 0 -> 4304 bytes test/constraint/parallel/free_in_3d.slvs | 295 + test/constraint/parallel/free_in_3d_v20.slvs | 290 + test/constraint/parallel/free_in_3d_v22.slvs | 290 + test/constraint/parallel/normal.png | Bin 0 -> 4686 bytes test/constraint/parallel/normal.slvs | 339 + test/constraint/parallel/normal_v20.slvs | 337 + test/constraint/parallel/normal_v22.slvs | 339 + test/constraint/parallel/test.cpp | 33 + test/constraint/perpendicular/normal.png | Bin 0 -> 4910 bytes test/constraint/perpendicular/normal.slvs | 339 + test/constraint/perpendicular/normal_v20.slvs | 337 + test/constraint/perpendicular/normal_v22.slvs | 339 + test/constraint/perpendicular/test.cpp | 17 + .../points_coincident/free_in_3d.png | Bin 0 -> 4241 bytes .../points_coincident/free_in_3d.slvs | 288 + .../points_coincident/free_in_3d_v20.slvs | 288 + .../points_coincident/free_in_3d_v22.slvs | 288 + test/constraint/points_coincident/normal.png | Bin 0 -> 4241 bytes test/constraint/points_coincident/normal.slvs | 287 + .../points_coincident/normal_v20.slvs | 287 + .../points_coincident/normal_v22.slvs | 287 + test/constraint/points_coincident/test.cpp | 33 + test/constraint/proj_pt_distance/normal.png | Bin 0 -> 4518 bytes test/constraint/proj_pt_distance/normal.slvs | 290 + .../proj_pt_distance/normal_v20.slvs | 293 + .../proj_pt_distance/normal_v22.slvs | 290 + .../constraint/proj_pt_distance/reference.png | Bin 0 -> 4566 bytes .../proj_pt_distance/reference.slvs | 290 + .../proj_pt_distance/reference_v20.slvs | 292 + .../proj_pt_distance/reference_v22.slvs | 290 + test/constraint/proj_pt_distance/test.cpp | 33 + test/constraint/pt_face_distance/normal.png | Bin 0 -> 4314 bytes test/constraint/pt_face_distance/normal.slvs | 713 ++ .../pt_face_distance/normal_v20.slvs | 712 ++ .../pt_face_distance/normal_v22.slvs | 681 ++ .../constraint/pt_face_distance/reference.png | Bin 0 -> 4378 bytes .../pt_face_distance/reference.slvs | 713 ++ .../pt_face_distance/reference_v20.slvs | 712 ++ .../pt_face_distance/reference_v22.slvs | 681 ++ test/constraint/pt_face_distance/test.cpp | 33 + test/constraint/pt_in_plane/normal.png | Bin 0 -> 4228 bytes test/constraint/pt_in_plane/normal.slvs | 261 + test/constraint/pt_in_plane/normal_v20.slvs | 259 + test/constraint/pt_in_plane/normal_v22.slvs | 261 + test/constraint/pt_in_plane/test.cpp | 17 + test/constraint/pt_line_distance/extended.png | Bin 0 -> 4527 bytes .../constraint/pt_line_distance/extended.slvs | 316 + .../pt_line_distance/free_in_3d.png | Bin 0 -> 4403 bytes .../pt_line_distance/free_in_3d.slvs | 316 + .../pt_line_distance/free_in_3d_v20.slvs | 314 + .../pt_line_distance/free_in_3d_v22.slvs | 316 + test/constraint/pt_line_distance/normal.png | Bin 0 -> 4403 bytes test/constraint/pt_line_distance/normal.slvs | 314 + .../pt_line_distance/normal_v20.slvs | 314 + .../pt_line_distance/normal_v22.slvs | 314 + .../constraint/pt_line_distance/reference.png | Bin 0 -> 4475 bytes .../pt_line_distance/reference.slvs | 314 + .../pt_line_distance/reference_v20.slvs | 314 + .../pt_line_distance/reference_v22.slvs | 314 + test/constraint/pt_line_distance/test.cpp | 54 + .../constraint/pt_on_circle/negative_dia.slvs | 364 + test/constraint/pt_on_circle/normal.png | Bin 0 -> 4884 bytes test/constraint/pt_on_circle/normal.slvs | 318 + test/constraint/pt_on_circle/normal_v20.slvs | 318 + test/constraint/pt_on_circle/normal_v22.slvs | 318 + test/constraint/pt_on_circle/test.cpp | 23 + test/constraint/pt_on_face/normal.png | Bin 0 -> 4662 bytes test/constraint/pt_on_face/normal.slvs | 703 ++ test/constraint/pt_on_face/normal_v20.slvs | 670 ++ test/constraint/pt_on_face/normal_v22.slvs | 687 ++ test/constraint/pt_on_face/test.cpp | 17 + .../constraint/pt_on_line/free_in_3d_v20.slvs | 315 + .../constraint/pt_on_line/free_in_3d_v22.slvs | 315 + .../constraint/pt_on_line/left_free_in_3d.png | Bin 0 -> 4319 bytes .../pt_on_line/left_free_in_3d.slvs | 320 + test/constraint/pt_on_line/normal.png | Bin 0 -> 4324 bytes test/constraint/pt_on_line/normal.slvs | 318 + test/constraint/pt_on_line/normal_v20.slvs | 315 + test/constraint/pt_on_line/normal_v22.slvs | 313 + .../pt_on_line/right_free_in_3d.png | Bin 0 -> 4316 bytes .../pt_on_line/right_free_in_3d.slvs | 320 + test/constraint/pt_on_line/test.cpp | 39 + test/constraint/pt_plane_distance/normal.png | Bin 0 -> 4345 bytes test/constraint/pt_plane_distance/normal.slvs | 264 + .../pt_plane_distance/normal_v20.slvs | 262 + .../pt_plane_distance/normal_v22.slvs | 264 + .../pt_plane_distance/reference.png | Bin 0 -> 4417 bytes .../pt_plane_distance/reference.slvs | 264 + .../pt_plane_distance/reference_v20.slvs | 264 + .../pt_plane_distance/reference_v22.slvs | 264 + test/constraint/pt_plane_distance/test.cpp | 33 + test/constraint/pt_pt_distance/free_in_3d.png | Bin 0 -> 4462 bytes .../constraint/pt_pt_distance/free_in_3d.slvs | 289 + .../pt_pt_distance/free_in_3d_v20.slvs | 289 + .../pt_pt_distance/free_in_3d_v22.slvs | 289 + test/constraint/pt_pt_distance/normal.png | Bin 0 -> 4467 bytes test/constraint/pt_pt_distance/normal.slvs | 289 + .../constraint/pt_pt_distance/normal_v20.slvs | 287 + .../constraint/pt_pt_distance/normal_v22.slvs | 289 + test/constraint/pt_pt_distance/reference.png | Bin 0 -> 4539 bytes test/constraint/pt_pt_distance/reference.slvs | 289 + .../pt_pt_distance/reference_v20.slvs | 289 + .../pt_pt_distance/reference_v22.slvs | 289 + test/constraint/pt_pt_distance/test.cpp | 49 + test/constraint/same_orientation/normal.png | Bin 0 -> 4472 bytes test/constraint/same_orientation/normal.slvs | 309 + .../same_orientation/normal_v20.slvs | 302 + .../same_orientation/normal_v22.slvs | 304 + .../same_orientation/same_group.png | Bin 0 -> 4376 bytes .../same_orientation/same_group.slvs | 379 + test/constraint/same_orientation/test.cpp | 23 + test/constraint/symmetric/free_in_3d.png | Bin 0 -> 4378 bytes test/constraint/symmetric/free_in_3d.slvs | 289 + test/constraint/symmetric/free_in_3d_v20.slvs | 287 + test/constraint/symmetric/free_in_3d_v22.slvs | 289 + test/constraint/symmetric/normal.png | Bin 0 -> 4362 bytes test/constraint/symmetric/normal.slvs | 288 + test/constraint/symmetric/normal_v20.slvs | 288 + test/constraint/symmetric/normal_v22.slvs | 288 + test/constraint/symmetric/test.cpp | 33 + test/constraint/symmetric_horiz/normal.png | Bin 0 -> 4362 bytes test/constraint/symmetric_horiz/normal.slvs | 287 + .../symmetric_horiz/normal_v20.slvs | 285 + .../symmetric_horiz/normal_v22.slvs | 287 + test/constraint/symmetric_horiz/test.cpp | 17 + test/constraint/symmetric_line/normal.png | Bin 0 -> 4431 bytes test/constraint/symmetric_line/normal.slvs | 338 + .../constraint/symmetric_line/normal_v20.slvs | 336 + .../constraint/symmetric_line/normal_v22.slvs | 338 + test/constraint/symmetric_line/test.cpp | 17 + test/constraint/symmetric_vert/normal.png | Bin 0 -> 4378 bytes test/constraint/symmetric_vert/normal.slvs | 287 + .../constraint/symmetric_vert/normal_v20.slvs | 285 + .../constraint/symmetric_vert/normal_v22.slvs | 287 + test/constraint/symmetric_vert/test.cpp | 17 + test/constraint/vertical/line.png | Bin 0 -> 4331 bytes test/constraint/vertical/line.slvs | 288 + test/constraint/vertical/line_v20.slvs | 290 + test/constraint/vertical/line_v22.slvs | 288 + test/constraint/vertical/pt_pt.png | Bin 0 -> 4258 bytes test/constraint/vertical/pt_pt.slvs | 287 + test/constraint/vertical/pt_pt_v20.slvs | 285 + test/constraint/vertical/pt_pt_v22.slvs | 287 + test/constraint/vertical/test.cpp | 33 + test/constraint/where_dragged/free_in_3d.png | Bin 0 -> 4257 bytes test/constraint/where_dragged/free_in_3d.slvs | 261 + .../where_dragged/free_in_3d_v20.slvs | 261 + .../where_dragged/free_in_3d_v22.slvs | 261 + test/constraint/where_dragged/normal.png | Bin 0 -> 4257 bytes test/constraint/where_dragged/normal.slvs | 262 + test/constraint/where_dragged/normal_v20.slvs | 260 + test/constraint/where_dragged/normal_v22.slvs | 262 + test/constraint/where_dragged/test.cpp | 33 + test/core/expr/test.cpp | 112 + test/core/locale/test.cpp | 8 + test/core/path/test.cpp | 245 + test/debugtool.cpp | 31 + test/group/link/normal.png | Bin 0 -> 4151 bytes test/group/link/normal.slvs | 465 + test/group/link/normal_v20.slvs | 463 + test/group/link/normal_v22.slvs | 465 + test/group/link/rect_v20.slvs | 510 + test/group/link/test.cpp | 17 + test/group/translate_asy/normal.png | Bin 0 -> 8508 bytes test/group/translate_asy/normal.slvs | 2088 +++++ test/group/translate_asy/normal_v22.slvs | 2088 +++++ test/group/translate_asy/test.cpp | 29 + test/group/translate_nd/normal.png | Bin 0 -> 4983 bytes test/group/translate_nd/normal.slvs | 8316 +++++++++++++++++ test/group/translate_nd/normal_v22.slvs | 8316 +++++++++++++++++ test/group/translate_nd/test.cpp | 12 + test/harness.cpp | 413 + test/harness.h | 75 + test/request/arc_of_circle/normal.png | Bin 0 -> 4560 bytes test/request/arc_of_circle/normal.slvs | 306 + test/request/arc_of_circle/normal_v20.slvs | 306 + test/request/arc_of_circle/normal_v22.slvs | 306 + test/request/arc_of_circle/test.cpp | 17 + test/request/circle/free_in_3d.png | Bin 0 -> 4796 bytes test/request/circle/free_in_3d.slvs | 294 + test/request/circle/free_in_3d_dof.png | Bin 0 -> 7195 bytes test/request/circle/free_in_3d_v20.slvs | 292 + test/request/circle/free_in_3d_v22.slvs | 294 + test/request/circle/normal.png | Bin 0 -> 4796 bytes test/request/circle/normal.slvs | 283 + test/request/circle/normal_dof.png | Bin 0 -> 4830 bytes test/request/circle/normal_v20.slvs | 283 + test/request/circle/normal_v22.slvs | 283 + test/request/circle/test.cpp | 45 + test/request/cubic/normal.png | Bin 0 -> 4949 bytes test/request/cubic/normal.slvs | 348 + test/request/cubic/normal_v20.slvs | 348 + test/request/cubic/normal_v22.slvs | 348 + test/request/cubic/test.cpp | 17 + test/request/cubic_periodic/normal.png | Bin 0 -> 5225 bytes test/request/cubic_periodic/normal.slvs | 316 + test/request/cubic_periodic/normal_v20.slvs | 314 + test/request/cubic_periodic/normal_v22.slvs | 316 + test/request/cubic_periodic/test.cpp | 17 + test/request/datum_point/normal.png | Bin 0 -> 4205 bytes test/request/datum_point/normal.slvs | 252 + test/request/datum_point/normal_v20.slvs | 250 + test/request/datum_point/normal_v22.slvs | 252 + test/request/datum_point/test.cpp | 17 + test/request/image/drawing.png | Bin 0 -> 8565 bytes test/request/image/linked.slvs | 430 + test/request/image/normal.slvs | 319 + test/request/image/test.cpp | 14 + test/request/line_segment/normal.png | Bin 0 -> 4246 bytes test/request/line_segment/normal.slvs | 278 + test/request/line_segment/normal_v20.slvs | 278 + test/request/line_segment/normal_v22.slvs | 278 + test/request/line_segment/test.cpp | 17 + test/request/ttf_text/normal.png | Bin 0 -> 6158 bytes test/request/ttf_text/normal.slvs | 329 + test/request/ttf_text/normal_v20.slvs | 290 + test/request/ttf_text/normal_v22.slvs | 292 + test/request/ttf_text/test.cpp | 17 + test/request/workplane/normal.png | Bin 0 -> 5612 bytes test/request/workplane/normal.slvs | 293 + test/request/workplane/normal_v20.slvs | 291 + test/request/workplane/normal_v22.slvs | 293 + test/request/workplane/test.cpp | 17 + tools/CMakeLists.txt | 23 - tools/lff2c.cpp | 410 - tools/png2c.cpp | 122 - tools/unifont2c.cpp | 261 - 897 files changed, 192458 insertions(+), 18061 deletions(-) create mode 100644 .clang-format create mode 100644 CONTRIBUTING.md create mode 100644 THIRD_PARTIES.txt delete mode 100644 appveyor.yml create mode 100644 bench/CMakeLists.txt create mode 100644 bench/harness.cpp create mode 100644 cmake/AddVendoredSubdirectory.cmake create mode 100644 cmake/DisableWarnings.cmake create mode 100644 cmake/FindVendoredPackage.cmake create mode 100644 developer_docs/Solver_Transforms.txt create mode 100644 extlib/mimalloc/.gitattributes create mode 100644 extlib/mimalloc/.gitignore create mode 100644 extlib/mimalloc/CMakeLists.txt create mode 100644 extlib/mimalloc/LICENSE create mode 100644 extlib/mimalloc/azure-pipelines.yml create mode 100644 extlib/mimalloc/bin/mimalloc-redirect.dll create mode 100644 extlib/mimalloc/bin/mimalloc-redirect.lib create mode 100644 extlib/mimalloc/bin/mimalloc-redirect32.dll create mode 100644 extlib/mimalloc/bin/mimalloc-redirect32.lib create mode 100644 extlib/mimalloc/cmake/mimalloc-config-version.cmake create mode 100644 extlib/mimalloc/cmake/mimalloc-config.cmake create mode 100644 extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-a.svg create mode 100644 extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-b.svg create mode 100644 extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-rss-a.svg create mode 100644 extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-rss-b.svg create mode 100644 extlib/mimalloc/doc/bench-r5a-1.svg create mode 100644 extlib/mimalloc/doc/bench-r5a-12xlarge-2020-01-16-a.svg create mode 100644 extlib/mimalloc/doc/bench-r5a-12xlarge-2020-01-16-b.svg create mode 100644 extlib/mimalloc/doc/bench-r5a-2.svg create mode 100644 extlib/mimalloc/doc/bench-r5a-rss-1.svg create mode 100644 extlib/mimalloc/doc/bench-r5a-rss-2.svg create mode 100644 extlib/mimalloc/doc/bench-spec-rss.svg create mode 100644 extlib/mimalloc/doc/bench-spec.svg create mode 100644 extlib/mimalloc/doc/bench-z4-1.svg create mode 100644 extlib/mimalloc/doc/bench-z4-2.svg create mode 100644 extlib/mimalloc/doc/bench-z4-rss-1.svg create mode 100644 extlib/mimalloc/doc/bench-z4-rss-2.svg create mode 100644 extlib/mimalloc/doc/doxyfile create mode 100644 extlib/mimalloc/doc/mimalloc-doc.h create mode 100644 extlib/mimalloc/doc/mimalloc-doxygen.css create mode 100644 extlib/mimalloc/doc/mimalloc-logo-100.png create mode 100644 extlib/mimalloc/doc/mimalloc-logo.png create mode 100644 extlib/mimalloc/doc/mimalloc-logo.svg create mode 100644 extlib/mimalloc/docs/annotated.html create mode 100644 extlib/mimalloc/docs/annotated_dup.js create mode 100644 extlib/mimalloc/docs/bc_s.png create mode 100644 extlib/mimalloc/docs/bdwn.png create mode 100644 extlib/mimalloc/docs/bench.html create mode 100644 extlib/mimalloc/docs/build.html create mode 100644 extlib/mimalloc/docs/classes.html create mode 100644 extlib/mimalloc/docs/closed.png create mode 100644 extlib/mimalloc/docs/doc.png create mode 100644 extlib/mimalloc/docs/doxygen.css create mode 100644 extlib/mimalloc/docs/doxygen.png create mode 100644 extlib/mimalloc/docs/dynsections.js create mode 100644 extlib/mimalloc/docs/environment.html create mode 100644 extlib/mimalloc/docs/folderclosed.png create mode 100644 extlib/mimalloc/docs/folderopen.png create mode 100644 extlib/mimalloc/docs/functions.html create mode 100644 extlib/mimalloc/docs/functions_vars.html create mode 100644 extlib/mimalloc/docs/group__aligned.html create mode 100644 extlib/mimalloc/docs/group__aligned.js create mode 100644 extlib/mimalloc/docs/group__analysis.html create mode 100644 extlib/mimalloc/docs/group__analysis.js create mode 100644 extlib/mimalloc/docs/group__analysis_structmi__heap__area__t.js create mode 100644 extlib/mimalloc/docs/group__cpp.html create mode 100644 extlib/mimalloc/docs/group__cpp.js create mode 100644 extlib/mimalloc/docs/group__extended.html create mode 100644 extlib/mimalloc/docs/group__extended.js create mode 100644 extlib/mimalloc/docs/group__heap.html create mode 100644 extlib/mimalloc/docs/group__heap.js create mode 100644 extlib/mimalloc/docs/group__malloc.html create mode 100644 extlib/mimalloc/docs/group__malloc.js create mode 100644 extlib/mimalloc/docs/group__options.html create mode 100644 extlib/mimalloc/docs/group__options.js create mode 100644 extlib/mimalloc/docs/group__posix.html create mode 100644 extlib/mimalloc/docs/group__posix.js create mode 100644 extlib/mimalloc/docs/group__typed.html create mode 100644 extlib/mimalloc/docs/group__typed.js create mode 100644 extlib/mimalloc/docs/group__zeroinit.html create mode 100644 extlib/mimalloc/docs/group__zeroinit.js create mode 100644 extlib/mimalloc/docs/index.html create mode 100644 extlib/mimalloc/docs/jquery.js create mode 100644 extlib/mimalloc/docs/mimalloc-doc_8h_source.html create mode 100644 extlib/mimalloc/docs/mimalloc-doxygen.css create mode 100644 extlib/mimalloc/docs/mimalloc-logo.svg create mode 100644 extlib/mimalloc/docs/modules.html create mode 100644 extlib/mimalloc/docs/modules.js create mode 100644 extlib/mimalloc/docs/nav_f.png create mode 100644 extlib/mimalloc/docs/nav_g.png create mode 100644 extlib/mimalloc/docs/nav_h.png create mode 100644 extlib/mimalloc/docs/navtree.css create mode 100644 extlib/mimalloc/docs/navtree.js create mode 100644 extlib/mimalloc/docs/navtreedata.js create mode 100644 extlib/mimalloc/docs/navtreeindex0.js create mode 100644 extlib/mimalloc/docs/open.png create mode 100644 extlib/mimalloc/docs/overrides.html create mode 100644 extlib/mimalloc/docs/pages.html create mode 100644 extlib/mimalloc/docs/resize.js create mode 100644 extlib/mimalloc/docs/search/all_0.html create mode 100644 extlib/mimalloc/docs/search/all_0.js create mode 100644 extlib/mimalloc/docs/search/all_1.html create mode 100644 extlib/mimalloc/docs/search/all_1.js create mode 100644 extlib/mimalloc/docs/search/all_2.html create mode 100644 extlib/mimalloc/docs/search/all_2.js create mode 100644 extlib/mimalloc/docs/search/all_3.html create mode 100644 extlib/mimalloc/docs/search/all_3.js create mode 100644 extlib/mimalloc/docs/search/all_4.html create mode 100644 extlib/mimalloc/docs/search/all_4.js create mode 100644 extlib/mimalloc/docs/search/all_5.html create mode 100644 extlib/mimalloc/docs/search/all_5.js create mode 100644 extlib/mimalloc/docs/search/all_6.html create mode 100644 extlib/mimalloc/docs/search/all_6.js create mode 100644 extlib/mimalloc/docs/search/all_7.html create mode 100644 extlib/mimalloc/docs/search/all_7.js create mode 100644 extlib/mimalloc/docs/search/all_8.html create mode 100644 extlib/mimalloc/docs/search/all_8.js create mode 100644 extlib/mimalloc/docs/search/all_9.html create mode 100644 extlib/mimalloc/docs/search/all_9.js create mode 100644 extlib/mimalloc/docs/search/all_a.html create mode 100644 extlib/mimalloc/docs/search/all_a.js create mode 100644 extlib/mimalloc/docs/search/all_b.html create mode 100644 extlib/mimalloc/docs/search/all_b.js create mode 100644 extlib/mimalloc/docs/search/all_c.html create mode 100644 extlib/mimalloc/docs/search/all_c.js create mode 100644 extlib/mimalloc/docs/search/all_d.html create mode 100644 extlib/mimalloc/docs/search/all_d.js create mode 100644 extlib/mimalloc/docs/search/classes_0.html create mode 100644 extlib/mimalloc/docs/search/classes_0.js create mode 100644 extlib/mimalloc/docs/search/close.png create mode 100644 extlib/mimalloc/docs/search/enums_0.html create mode 100644 extlib/mimalloc/docs/search/enums_0.js create mode 100644 extlib/mimalloc/docs/search/enumvalues_0.html create mode 100644 extlib/mimalloc/docs/search/enumvalues_0.js create mode 100644 extlib/mimalloc/docs/search/enumvalues_1.html create mode 100644 extlib/mimalloc/docs/search/enumvalues_1.js create mode 100644 extlib/mimalloc/docs/search/functions_0.html create mode 100644 extlib/mimalloc/docs/search/functions_0.js create mode 100644 extlib/mimalloc/docs/search/functions_1.html create mode 100644 extlib/mimalloc/docs/search/functions_1.js create mode 100644 extlib/mimalloc/docs/search/groups_0.html create mode 100644 extlib/mimalloc/docs/search/groups_0.js create mode 100644 extlib/mimalloc/docs/search/groups_1.html create mode 100644 extlib/mimalloc/docs/search/groups_1.js create mode 100644 extlib/mimalloc/docs/search/groups_2.html create mode 100644 extlib/mimalloc/docs/search/groups_2.js create mode 100644 extlib/mimalloc/docs/search/groups_3.html create mode 100644 extlib/mimalloc/docs/search/groups_3.js create mode 100644 extlib/mimalloc/docs/search/groups_4.html create mode 100644 extlib/mimalloc/docs/search/groups_4.js create mode 100644 extlib/mimalloc/docs/search/groups_5.html create mode 100644 extlib/mimalloc/docs/search/groups_5.js create mode 100644 extlib/mimalloc/docs/search/groups_6.html create mode 100644 extlib/mimalloc/docs/search/groups_6.js create mode 100644 extlib/mimalloc/docs/search/groups_7.html create mode 100644 extlib/mimalloc/docs/search/groups_7.js create mode 100644 extlib/mimalloc/docs/search/groups_8.html create mode 100644 extlib/mimalloc/docs/search/groups_8.js create mode 100644 extlib/mimalloc/docs/search/mag_sel.png create mode 100644 extlib/mimalloc/docs/search/nomatches.html create mode 100644 extlib/mimalloc/docs/search/pages_0.html create mode 100644 extlib/mimalloc/docs/search/pages_0.js create mode 100644 extlib/mimalloc/docs/search/pages_1.html create mode 100644 extlib/mimalloc/docs/search/pages_1.js create mode 100644 extlib/mimalloc/docs/search/pages_2.html create mode 100644 extlib/mimalloc/docs/search/pages_2.js create mode 100644 extlib/mimalloc/docs/search/pages_3.html create mode 100644 extlib/mimalloc/docs/search/pages_3.js create mode 100644 extlib/mimalloc/docs/search/pages_4.html create mode 100644 extlib/mimalloc/docs/search/pages_4.js create mode 100644 extlib/mimalloc/docs/search/search.css create mode 100644 extlib/mimalloc/docs/search/search.js create mode 100644 extlib/mimalloc/docs/search/search_l.png create mode 100644 extlib/mimalloc/docs/search/search_m.png create mode 100644 extlib/mimalloc/docs/search/search_r.png create mode 100644 extlib/mimalloc/docs/search/searchdata.js create mode 100644 extlib/mimalloc/docs/search/typedefs_0.html create mode 100644 extlib/mimalloc/docs/search/typedefs_0.js create mode 100644 extlib/mimalloc/docs/search/typedefs_1.html create mode 100644 extlib/mimalloc/docs/search/typedefs_1.js create mode 100644 extlib/mimalloc/docs/search/typedefs_2.html create mode 100644 extlib/mimalloc/docs/search/typedefs_2.js create mode 100644 extlib/mimalloc/docs/search/variables_0.html create mode 100644 extlib/mimalloc/docs/search/variables_0.js create mode 100644 extlib/mimalloc/docs/search/variables_1.html create mode 100644 extlib/mimalloc/docs/search/variables_1.js create mode 100644 extlib/mimalloc/docs/search/variables_2.html create mode 100644 extlib/mimalloc/docs/search/variables_2.js create mode 100644 extlib/mimalloc/docs/search/variables_3.html create mode 100644 extlib/mimalloc/docs/search/variables_3.js create mode 100644 extlib/mimalloc/docs/splitbar.png create mode 100644 extlib/mimalloc/docs/sync_off.png create mode 100644 extlib/mimalloc/docs/sync_on.png create mode 100644 extlib/mimalloc/docs/tab_a.png create mode 100644 extlib/mimalloc/docs/tab_b.png create mode 100644 extlib/mimalloc/docs/tab_h.png create mode 100644 extlib/mimalloc/docs/tab_s.png create mode 100644 extlib/mimalloc/docs/tabs.css create mode 100644 extlib/mimalloc/docs/using.html create mode 100644 extlib/mimalloc/ide/vs2017/mimalloc-override-test.vcxproj create mode 100644 extlib/mimalloc/ide/vs2017/mimalloc-override-test.vcxproj.filters create mode 100644 extlib/mimalloc/ide/vs2017/mimalloc-override.vcxproj create mode 100644 extlib/mimalloc/ide/vs2017/mimalloc-override.vcxproj.filters create mode 100644 extlib/mimalloc/ide/vs2017/mimalloc-test-stress.vcxproj create mode 100644 extlib/mimalloc/ide/vs2017/mimalloc-test-stress.vcxproj.filters create mode 100644 extlib/mimalloc/ide/vs2017/mimalloc-test.vcxproj create mode 100644 extlib/mimalloc/ide/vs2017/mimalloc-test.vcxproj.filters create mode 100644 extlib/mimalloc/ide/vs2017/mimalloc.sln create mode 100644 extlib/mimalloc/ide/vs2017/mimalloc.vcxproj create mode 100644 extlib/mimalloc/ide/vs2017/mimalloc.vcxproj.filters create mode 100644 extlib/mimalloc/ide/vs2019/mimalloc-override-test.vcxproj create mode 100644 extlib/mimalloc/ide/vs2019/mimalloc-override.vcxproj create mode 100644 extlib/mimalloc/ide/vs2019/mimalloc-override.vcxproj.filters create mode 100644 extlib/mimalloc/ide/vs2019/mimalloc-test-api.vcxproj create mode 100644 extlib/mimalloc/ide/vs2019/mimalloc-test-stress.vcxproj create mode 100644 extlib/mimalloc/ide/vs2019/mimalloc-test.vcxproj create mode 100644 extlib/mimalloc/ide/vs2019/mimalloc.sln create mode 100644 extlib/mimalloc/ide/vs2019/mimalloc.vcxproj create mode 100644 extlib/mimalloc/ide/vs2019/mimalloc.vcxproj.filters create mode 100644 extlib/mimalloc/include/mimalloc-atomic.h create mode 100644 extlib/mimalloc/include/mimalloc-internal.h create mode 100644 extlib/mimalloc/include/mimalloc-new-delete.h create mode 100644 extlib/mimalloc/include/mimalloc-override.h create mode 100644 extlib/mimalloc/include/mimalloc-types.h create mode 100644 extlib/mimalloc/include/mimalloc.h create mode 100644 extlib/mimalloc/readme.md create mode 100644 extlib/mimalloc/src/alloc-aligned.c create mode 100644 extlib/mimalloc/src/alloc-override-osx.c create mode 100644 extlib/mimalloc/src/alloc-override.c create mode 100644 extlib/mimalloc/src/alloc-posix.c create mode 100644 extlib/mimalloc/src/alloc.c create mode 100644 extlib/mimalloc/src/arena.c create mode 100644 extlib/mimalloc/src/bitmap.inc.c create mode 100644 extlib/mimalloc/src/heap.c create mode 100644 extlib/mimalloc/src/init.c create mode 100644 extlib/mimalloc/src/options.c create mode 100644 extlib/mimalloc/src/os.c create mode 100644 extlib/mimalloc/src/page-queue.c create mode 100644 extlib/mimalloc/src/page.c create mode 100644 extlib/mimalloc/src/random.c create mode 100644 extlib/mimalloc/src/region.c create mode 100644 extlib/mimalloc/src/segment.c create mode 100644 extlib/mimalloc/src/static.c create mode 100644 extlib/mimalloc/src/stats.c create mode 100644 extlib/mimalloc/test/CMakeLists.txt create mode 100644 extlib/mimalloc/test/main-override-static.c create mode 100644 extlib/mimalloc/test/main-override.c create mode 100644 extlib/mimalloc/test/main-override.cpp create mode 100644 extlib/mimalloc/test/main.c create mode 100644 extlib/mimalloc/test/readme.md create mode 100644 extlib/mimalloc/test/test-api.c create mode 100644 extlib/mimalloc/test/test-stress.c create mode 100644 pkg/flatpak/.gitignore create mode 100755 pkg/flatpak/build.sh create mode 100644 pkg/flatpak/com.solvespace.SolveSpace.json create mode 100644 pkg/snap/.gitignore create mode 100755 pkg/snap/build.sh create mode 100644 pkg/snap/snap/snapcraft.yaml create mode 100644 res/CMakeLists.txt create mode 100644 res/banner.txt rename {src => res}/cocoa/AppIcon.iconset/icon_128x128.png (100%) rename {src => res}/cocoa/AppIcon.iconset/icon_128x128@2x.png (100%) rename {src => res}/cocoa/AppIcon.iconset/icon_16x16.png (100%) rename {src => res}/cocoa/AppIcon.iconset/icon_16x16@2x.png (100%) rename {src => res}/cocoa/AppIcon.iconset/icon_256x256.png (100%) rename {src => res}/cocoa/AppIcon.iconset/icon_256x256@2x.png (100%) rename {src => res}/cocoa/AppIcon.iconset/icon_32x32.png (100%) rename {src => res}/cocoa/AppIcon.iconset/icon_32x32@2x.png (100%) rename {src => res}/cocoa/AppIcon.iconset/icon_512x512.png (100%) rename {src => res}/cocoa/AppIcon.iconset/icon_512x512@2x.png (100%) rename {src => res}/cocoa/MainMenu.xib (99%) rename {src => res}/cocoa/SaveFormatAccessory.xib (97%) create mode 100644 res/fonts/BitstreamVeraSans-Roman-builtin.ttf rename {src => res}/fonts/private/0-check-false.png (100%) rename {src => res}/fonts/private/1-check-true.png (100%) rename {src => res}/fonts/private/2-radio-false.png (100%) rename {src => res}/fonts/private/3-radio-true.png (100%) rename {src => res}/fonts/private/4-stipple-dot.png (100%) rename {src => res}/fonts/private/5-stipple-dash-long.png (100%) rename {src => res}/fonts/private/6-stipple-dash.png (100%) rename {src => res}/fonts/private/7-stipple-zigzag.png (100%) rename {src => res}/fonts/unicode.lff.gz (100%) create mode 100644 res/fonts/unifont.hex.gz rename {src/unix => res/freedesktop}/solvespace-16x16.png (100%) rename {src/unix => res/freedesktop}/solvespace-16x16.xpm (100%) rename {src/unix => res/freedesktop}/solvespace-24x24.png (100%) rename {src/unix => res/freedesktop}/solvespace-24x24.xpm (100%) rename {src/unix => res/freedesktop}/solvespace-32x32.png (100%) rename {src/unix => res/freedesktop}/solvespace-32x32.xpm (100%) rename {src/unix => res/freedesktop}/solvespace-48x48.png (100%) rename {src/unix => res/freedesktop}/solvespace-48x48.xpm (100%) create mode 100644 res/freedesktop/solvespace-flatpak-mime.xml create mode 100644 res/freedesktop/solvespace-flatpak.desktop.in create mode 100644 res/freedesktop/solvespace-mime.xml create mode 100644 res/freedesktop/solvespace-scalable.svg create mode 100644 res/freedesktop/solvespace-snap.desktop rename src/unix/solvespace.desktop => res/freedesktop/solvespace.desktop.in (66%) create mode 100644 res/icons/graphics-window/angle.png create mode 100644 res/icons/graphics-window/arc.png create mode 100644 res/icons/graphics-window/assemble.png create mode 100644 res/icons/graphics-window/bezier.png create mode 100644 res/icons/graphics-window/circle.png create mode 100644 res/icons/graphics-window/construction.png create mode 100644 res/icons/graphics-window/equal.png create mode 100644 res/icons/graphics-window/extrude.png create mode 100644 res/icons/graphics-window/horiz.png create mode 100644 res/icons/graphics-window/image.png create mode 100644 res/icons/graphics-window/in3d.png create mode 100644 res/icons/graphics-window/lathe.png create mode 100644 res/icons/graphics-window/length.png create mode 100644 res/icons/graphics-window/line.png create mode 100644 res/icons/graphics-window/ontoworkplane.png create mode 100644 res/icons/graphics-window/other-supp.png create mode 100644 res/icons/graphics-window/parallel.png create mode 100644 res/icons/graphics-window/perpendicular.png create mode 100644 res/icons/graphics-window/point.png create mode 100644 res/icons/graphics-window/pointonx.png create mode 100644 res/icons/graphics-window/rectangle.png create mode 100644 res/icons/graphics-window/ref.png create mode 100644 res/icons/graphics-window/same-orientation.png create mode 100644 res/icons/graphics-window/sketch-in-3d.png create mode 100644 res/icons/graphics-window/sketch-in-plane.png create mode 100644 res/icons/graphics-window/step-rotate.png create mode 100644 res/icons/graphics-window/step-translate.png create mode 100644 res/icons/graphics-window/symmetric.png create mode 100644 res/icons/graphics-window/tangent-arc.png create mode 100644 res/icons/graphics-window/text.png create mode 100644 res/icons/graphics-window/trim.png create mode 100644 res/icons/graphics-window/vert.png create mode 100644 res/icons/text-window/constraint.png create mode 100644 res/icons/text-window/construction.png create mode 100644 res/icons/text-window/edges.png create mode 100644 res/icons/text-window/faces.png create mode 100644 res/icons/text-window/mesh.png create mode 100644 res/icons/text-window/normal.png create mode 100644 res/icons/text-window/occluded-invisible.png create mode 100644 res/icons/text-window/occluded-stippled.png create mode 100644 res/icons/text-window/occluded-visible.png create mode 100644 res/icons/text-window/outlines.png create mode 100644 res/icons/text-window/point.png create mode 100644 res/icons/text-window/shaded.png create mode 100644 res/icons/text-window/workplane.png create mode 100644 res/locales.txt create mode 100644 res/locales/de_DE.po create mode 100644 res/locales/en_US.po create mode 100644 res/locales/fr_FR.po create mode 100644 res/locales/ru_RU.po create mode 100644 res/locales/uk_UA.po create mode 100644 res/locales/zh_CN.po create mode 100644 res/messages.pot create mode 100644 res/shaders/edge.frag create mode 100644 res/shaders/edge.vert create mode 100644 res/shaders/imesh.frag create mode 100644 res/shaders/imesh.vert create mode 100644 res/shaders/imesh_point.frag create mode 100644 res/shaders/imesh_point.vert create mode 100644 res/shaders/imesh_tex.frag create mode 100644 res/shaders/imesh_tex.vert create mode 100644 res/shaders/imesh_texa.frag create mode 100644 res/shaders/mesh.frag create mode 100644 res/shaders/mesh.vert create mode 100644 res/shaders/mesh_fill.frag create mode 100644 res/shaders/mesh_fill.vert create mode 100644 res/shaders/outline.vert create mode 100644 res/threejs/SolveSpaceControls.js create mode 100644 res/threejs/hammer-2.0.8.js.gz create mode 100644 res/threejs/three-r76.js.gz rename {src => res}/win32/icon.ico (100%) rename {src => res}/win32/manifest.xml (50%) create mode 100644 res/win32/versioninfo.rc.in delete mode 100644 src/cocoa/cocoamain.mm delete mode 100644 src/glhelper.cpp delete mode 100644 src/gtk/gtkmain.cpp delete mode 100644 src/icons/angle.png delete mode 100644 src/icons/arc.png delete mode 100644 src/icons/assemble.png delete mode 100644 src/icons/bezier.png delete mode 100644 src/icons/circle.png delete mode 100644 src/icons/constraint.png delete mode 100644 src/icons/construction.png delete mode 100644 src/icons/edges.png delete mode 100644 src/icons/equal.png delete mode 100644 src/icons/extrude.png delete mode 100644 src/icons/faces.png delete mode 100644 src/icons/hidden-lines.png delete mode 100644 src/icons/horiz.png delete mode 100644 src/icons/in3d.png delete mode 100644 src/icons/lathe.png delete mode 100644 src/icons/length.png delete mode 100644 src/icons/line.png delete mode 100644 src/icons/mesh.png delete mode 100644 src/icons/normal.png delete mode 100644 src/icons/ontoworkplane.png delete mode 100644 src/icons/other-supp.png delete mode 100644 src/icons/outlines.png delete mode 100644 src/icons/parallel.png delete mode 100644 src/icons/perpendicular.png delete mode 100644 src/icons/point.png delete mode 100644 src/icons/pointonx.png delete mode 100644 src/icons/rectangle.png delete mode 100644 src/icons/ref.png delete mode 100644 src/icons/same-orientation.png delete mode 100644 src/icons/shaded.png delete mode 100644 src/icons/sketch-in-3d.png delete mode 100644 src/icons/sketch-in-plane.png delete mode 100644 src/icons/step-rotate.png delete mode 100644 src/icons/step-translate.png delete mode 100644 src/icons/symmetric.png delete mode 100644 src/icons/tangent-arc.png delete mode 100644 src/icons/text.png delete mode 100644 src/icons/trim.png delete mode 100644 src/icons/vert.png delete mode 100644 src/icons/workplane.png create mode 100644 src/importidf.cpp create mode 100644 src/platform/entrycli.cpp create mode 100644 src/platform/entrygui.cpp create mode 100644 src/platform/gui.cpp create mode 100644 src/platform/gui.h create mode 100644 src/platform/guigtk.cpp create mode 100644 src/platform/guimac.mm create mode 100644 src/platform/guinone.cpp create mode 100644 src/platform/guiwin.cpp create mode 100644 src/platform/platform.cpp create mode 100644 src/platform/platform.h create mode 100644 src/polyline.cpp create mode 100644 src/render/gl3shader.cpp create mode 100644 src/render/gl3shader.h create mode 100644 src/render/render.cpp create mode 100644 src/render/render.h create mode 100644 src/render/render2d.cpp create mode 100644 src/render/rendercairo.cpp create mode 100644 src/render/rendergl1.cpp create mode 100644 src/render/rendergl3.cpp create mode 100644 src/resource.cpp create mode 100644 src/resource.h delete mode 100644 src/unix/gloffscreen.cpp delete mode 100644 src/unix/gloffscreen.h delete mode 100644 src/unix/unixutil.cpp delete mode 100644 src/win32/resource.rc delete mode 100644 src/win32/w32main.cpp delete mode 100644 src/win32/w32util.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/Gentium-R.ttf create mode 100644 test/analysis/contour_area/normal.png create mode 100644 test/analysis/contour_area/normal.slvs create mode 100644 test/analysis/contour_area/test.cpp create mode 100755 test/commit.sh create mode 100644 test/constraint/angle/free_in_3d.png create mode 100644 test/constraint/angle/free_in_3d.slvs create mode 100644 test/constraint/angle/free_in_3d_v20.slvs create mode 100644 test/constraint/angle/free_in_3d_v22.slvs create mode 100644 test/constraint/angle/normal.png create mode 100644 test/constraint/angle/normal.slvs create mode 100644 test/constraint/angle/normal_v20.slvs create mode 100644 test/constraint/angle/normal_v22.slvs create mode 100644 test/constraint/angle/reference.png create mode 100644 test/constraint/angle/reference.slvs create mode 100644 test/constraint/angle/reference_free_in_3d.png create mode 100644 test/constraint/angle/reference_free_in_3d.slvs create mode 100644 test/constraint/angle/reference_free_in_3d_v20.slvs create mode 100644 test/constraint/angle/reference_free_in_3d_v22.slvs create mode 100644 test/constraint/angle/reference_v20.slvs create mode 100644 test/constraint/angle/reference_v22.slvs create mode 100644 test/constraint/angle/skew.png create mode 100644 test/constraint/angle/skew.slvs create mode 100644 test/constraint/angle/skew_v22.slvs create mode 100644 test/constraint/angle/test.cpp create mode 100644 test/constraint/arc_line_tangent/normal.png create mode 100644 test/constraint/arc_line_tangent/normal.slvs create mode 100644 test/constraint/arc_line_tangent/normal_v20.slvs create mode 100644 test/constraint/arc_line_tangent/normal_v22.slvs create mode 100644 test/constraint/arc_line_tangent/test.cpp create mode 100644 test/constraint/at_midpoint/line_plane_free_in_3d.png create mode 100644 test/constraint/at_midpoint/line_plane_free_in_3d.slvs create mode 100644 test/constraint/at_midpoint/line_plane_free_in_3d_v20.slvs create mode 100644 test/constraint/at_midpoint/line_plane_free_in_3d_v22.slvs create mode 100644 test/constraint/at_midpoint/line_plane_normal.png create mode 100644 test/constraint/at_midpoint/line_plane_normal.slvs create mode 100644 test/constraint/at_midpoint/line_plane_normal_v20.slvs create mode 100644 test/constraint/at_midpoint/line_plane_normal_v22.slvs create mode 100644 test/constraint/at_midpoint/line_pt_free_in_3d.png create mode 100644 test/constraint/at_midpoint/line_pt_free_in_3d.slvs create mode 100644 test/constraint/at_midpoint/line_pt_free_in_3d_v20.slvs create mode 100644 test/constraint/at_midpoint/line_pt_free_in_3d_v22.slvs create mode 100644 test/constraint/at_midpoint/line_pt_normal.png create mode 100644 test/constraint/at_midpoint/line_pt_normal.slvs create mode 100644 test/constraint/at_midpoint/line_pt_normal_v20.slvs create mode 100644 test/constraint/at_midpoint/line_pt_normal_v22.slvs create mode 100644 test/constraint/at_midpoint/test.cpp create mode 100644 test/constraint/comment/normal.png create mode 100644 test/constraint/comment/normal.slvs create mode 100644 test/constraint/comment/normal_v20.slvs create mode 100644 test/constraint/comment/normal_v22.slvs create mode 100644 test/constraint/comment/test.cpp create mode 100644 test/constraint/cubic_line_tangent/free_in_3d.png create mode 100644 test/constraint/cubic_line_tangent/free_in_3d.slvs create mode 100644 test/constraint/cubic_line_tangent/free_in_3d_v20.slvs create mode 100644 test/constraint/cubic_line_tangent/free_in_3d_v22.slvs create mode 100644 test/constraint/cubic_line_tangent/normal.png create mode 100644 test/constraint/cubic_line_tangent/normal.slvs create mode 100644 test/constraint/cubic_line_tangent/normal_v20.slvs create mode 100644 test/constraint/cubic_line_tangent/normal_v22.slvs create mode 100644 test/constraint/cubic_line_tangent/test.cpp create mode 100644 test/constraint/curve_curve_tangent/arc_arc.png create mode 100644 test/constraint/curve_curve_tangent/arc_arc.slvs create mode 100644 test/constraint/curve_curve_tangent/arc_arc_v20.slvs create mode 100644 test/constraint/curve_curve_tangent/arc_arc_v22.slvs create mode 100644 test/constraint/curve_curve_tangent/arc_cubic.png create mode 100644 test/constraint/curve_curve_tangent/arc_cubic.slvs create mode 100644 test/constraint/curve_curve_tangent/arc_cubic_v20.slvs create mode 100644 test/constraint/curve_curve_tangent/arc_cubic_v22.slvs create mode 100644 test/constraint/curve_curve_tangent/test.cpp create mode 100644 test/constraint/diameter/normal.png create mode 100644 test/constraint/diameter/normal.slvs create mode 100644 test/constraint/diameter/normal_v20.slvs create mode 100644 test/constraint/diameter/normal_v22.slvs create mode 100644 test/constraint/diameter/reference.png create mode 100644 test/constraint/diameter/reference.slvs create mode 100644 test/constraint/diameter/reference_v20.slvs create mode 100644 test/constraint/diameter/reference_v22.slvs create mode 100644 test/constraint/diameter/test.cpp create mode 100644 test/constraint/eq_len_pt_line_d/normal.png create mode 100644 test/constraint/eq_len_pt_line_d/normal.slvs create mode 100644 test/constraint/eq_len_pt_line_d/normal_v20.slvs create mode 100644 test/constraint/eq_len_pt_line_d/normal_v22.slvs create mode 100644 test/constraint/eq_len_pt_line_d/test.cpp create mode 100644 test/constraint/eq_pt_ln_distances/normal.png create mode 100644 test/constraint/eq_pt_ln_distances/normal.slvs create mode 100644 test/constraint/eq_pt_ln_distances/normal_v20.slvs create mode 100644 test/constraint/eq_pt_ln_distances/normal_v22.slvs create mode 100644 test/constraint/eq_pt_ln_distances/test.cpp create mode 100644 test/constraint/equal_angle/normal.png create mode 100644 test/constraint/equal_angle/normal.slvs create mode 100644 test/constraint/equal_angle/normal_v20.slvs create mode 100644 test/constraint/equal_angle/normal_v22.slvs create mode 100644 test/constraint/equal_angle/other.png create mode 100644 test/constraint/equal_angle/other.slvs create mode 100644 test/constraint/equal_angle/other_v20.slvs create mode 100644 test/constraint/equal_angle/other_v22.slvs create mode 100644 test/constraint/equal_angle/test.cpp create mode 100644 test/constraint/equal_length_lines/normal.png create mode 100644 test/constraint/equal_length_lines/normal.slvs create mode 100644 test/constraint/equal_length_lines/normal_v20.slvs create mode 100644 test/constraint/equal_length_lines/normal_v22.slvs create mode 100644 test/constraint/equal_length_lines/test.cpp create mode 100644 test/constraint/equal_line_arc_len/normal.png create mode 100644 test/constraint/equal_line_arc_len/normal.slvs create mode 100644 test/constraint/equal_line_arc_len/normal_v20.slvs create mode 100644 test/constraint/equal_line_arc_len/normal_v22.slvs create mode 100644 test/constraint/equal_line_arc_len/pi.png create mode 100644 test/constraint/equal_line_arc_len/pi.slvs create mode 100644 test/constraint/equal_line_arc_len/tau.png create mode 100644 test/constraint/equal_line_arc_len/tau.slvs create mode 100644 test/constraint/equal_line_arc_len/test.cpp create mode 100644 test/constraint/equal_radius/normal.png create mode 100644 test/constraint/equal_radius/normal.slvs create mode 100644 test/constraint/equal_radius/normal_v20.slvs create mode 100644 test/constraint/equal_radius/normal_v22.slvs create mode 100644 test/constraint/equal_radius/test.cpp create mode 100644 test/constraint/horizontal/line.png create mode 100644 test/constraint/horizontal/line.slvs create mode 100644 test/constraint/horizontal/line_v20.slvs create mode 100644 test/constraint/horizontal/line_v22.slvs create mode 100644 test/constraint/horizontal/pt_pt.png create mode 100644 test/constraint/horizontal/pt_pt.slvs create mode 100644 test/constraint/horizontal/pt_pt_v20.slvs create mode 100644 test/constraint/horizontal/pt_pt_v22.slvs create mode 100644 test/constraint/horizontal/test.cpp create mode 100644 test/constraint/length_difference/normal.png create mode 100644 test/constraint/length_difference/normal.slvs create mode 100644 test/constraint/length_difference/normal_v22.slvs create mode 100644 test/constraint/length_difference/reference.png create mode 100644 test/constraint/length_difference/reference.slvs create mode 100644 test/constraint/length_difference/reference_v22.slvs create mode 100644 test/constraint/length_difference/test.cpp create mode 100644 test/constraint/length_ratio/normal.png create mode 100644 test/constraint/length_ratio/normal.slvs create mode 100644 test/constraint/length_ratio/normal_v20.slvs create mode 100644 test/constraint/length_ratio/normal_v22.slvs create mode 100644 test/constraint/length_ratio/reference.png create mode 100644 test/constraint/length_ratio/reference.slvs create mode 100644 test/constraint/length_ratio/reference_v20.slvs create mode 100644 test/constraint/length_ratio/reference_v22.slvs create mode 100644 test/constraint/length_ratio/test.cpp create mode 100644 test/constraint/parallel/free_in_3d.png create mode 100644 test/constraint/parallel/free_in_3d.slvs create mode 100644 test/constraint/parallel/free_in_3d_v20.slvs create mode 100644 test/constraint/parallel/free_in_3d_v22.slvs create mode 100644 test/constraint/parallel/normal.png create mode 100644 test/constraint/parallel/normal.slvs create mode 100644 test/constraint/parallel/normal_v20.slvs create mode 100644 test/constraint/parallel/normal_v22.slvs create mode 100644 test/constraint/parallel/test.cpp create mode 100644 test/constraint/perpendicular/normal.png create mode 100644 test/constraint/perpendicular/normal.slvs create mode 100644 test/constraint/perpendicular/normal_v20.slvs create mode 100644 test/constraint/perpendicular/normal_v22.slvs create mode 100644 test/constraint/perpendicular/test.cpp create mode 100644 test/constraint/points_coincident/free_in_3d.png create mode 100644 test/constraint/points_coincident/free_in_3d.slvs create mode 100644 test/constraint/points_coincident/free_in_3d_v20.slvs create mode 100644 test/constraint/points_coincident/free_in_3d_v22.slvs create mode 100644 test/constraint/points_coincident/normal.png create mode 100644 test/constraint/points_coincident/normal.slvs create mode 100644 test/constraint/points_coincident/normal_v20.slvs create mode 100644 test/constraint/points_coincident/normal_v22.slvs create mode 100644 test/constraint/points_coincident/test.cpp create mode 100644 test/constraint/proj_pt_distance/normal.png create mode 100644 test/constraint/proj_pt_distance/normal.slvs create mode 100644 test/constraint/proj_pt_distance/normal_v20.slvs create mode 100644 test/constraint/proj_pt_distance/normal_v22.slvs create mode 100644 test/constraint/proj_pt_distance/reference.png create mode 100644 test/constraint/proj_pt_distance/reference.slvs create mode 100644 test/constraint/proj_pt_distance/reference_v20.slvs create mode 100644 test/constraint/proj_pt_distance/reference_v22.slvs create mode 100644 test/constraint/proj_pt_distance/test.cpp create mode 100644 test/constraint/pt_face_distance/normal.png create mode 100644 test/constraint/pt_face_distance/normal.slvs create mode 100644 test/constraint/pt_face_distance/normal_v20.slvs create mode 100644 test/constraint/pt_face_distance/normal_v22.slvs create mode 100644 test/constraint/pt_face_distance/reference.png create mode 100644 test/constraint/pt_face_distance/reference.slvs create mode 100644 test/constraint/pt_face_distance/reference_v20.slvs create mode 100644 test/constraint/pt_face_distance/reference_v22.slvs create mode 100644 test/constraint/pt_face_distance/test.cpp create mode 100644 test/constraint/pt_in_plane/normal.png create mode 100644 test/constraint/pt_in_plane/normal.slvs create mode 100644 test/constraint/pt_in_plane/normal_v20.slvs create mode 100644 test/constraint/pt_in_plane/normal_v22.slvs create mode 100644 test/constraint/pt_in_plane/test.cpp create mode 100644 test/constraint/pt_line_distance/extended.png create mode 100644 test/constraint/pt_line_distance/extended.slvs create mode 100644 test/constraint/pt_line_distance/free_in_3d.png create mode 100644 test/constraint/pt_line_distance/free_in_3d.slvs create mode 100644 test/constraint/pt_line_distance/free_in_3d_v20.slvs create mode 100644 test/constraint/pt_line_distance/free_in_3d_v22.slvs create mode 100644 test/constraint/pt_line_distance/normal.png create mode 100644 test/constraint/pt_line_distance/normal.slvs create mode 100644 test/constraint/pt_line_distance/normal_v20.slvs create mode 100644 test/constraint/pt_line_distance/normal_v22.slvs create mode 100644 test/constraint/pt_line_distance/reference.png create mode 100644 test/constraint/pt_line_distance/reference.slvs create mode 100644 test/constraint/pt_line_distance/reference_v20.slvs create mode 100644 test/constraint/pt_line_distance/reference_v22.slvs create mode 100644 test/constraint/pt_line_distance/test.cpp create mode 100644 test/constraint/pt_on_circle/negative_dia.slvs create mode 100644 test/constraint/pt_on_circle/normal.png create mode 100644 test/constraint/pt_on_circle/normal.slvs create mode 100644 test/constraint/pt_on_circle/normal_v20.slvs create mode 100644 test/constraint/pt_on_circle/normal_v22.slvs create mode 100644 test/constraint/pt_on_circle/test.cpp create mode 100644 test/constraint/pt_on_face/normal.png create mode 100644 test/constraint/pt_on_face/normal.slvs create mode 100644 test/constraint/pt_on_face/normal_v20.slvs create mode 100644 test/constraint/pt_on_face/normal_v22.slvs create mode 100644 test/constraint/pt_on_face/test.cpp create mode 100644 test/constraint/pt_on_line/free_in_3d_v20.slvs create mode 100644 test/constraint/pt_on_line/free_in_3d_v22.slvs create mode 100644 test/constraint/pt_on_line/left_free_in_3d.png create mode 100644 test/constraint/pt_on_line/left_free_in_3d.slvs create mode 100644 test/constraint/pt_on_line/normal.png create mode 100644 test/constraint/pt_on_line/normal.slvs create mode 100644 test/constraint/pt_on_line/normal_v20.slvs create mode 100644 test/constraint/pt_on_line/normal_v22.slvs create mode 100644 test/constraint/pt_on_line/right_free_in_3d.png create mode 100644 test/constraint/pt_on_line/right_free_in_3d.slvs create mode 100644 test/constraint/pt_on_line/test.cpp create mode 100644 test/constraint/pt_plane_distance/normal.png create mode 100644 test/constraint/pt_plane_distance/normal.slvs create mode 100644 test/constraint/pt_plane_distance/normal_v20.slvs create mode 100644 test/constraint/pt_plane_distance/normal_v22.slvs create mode 100644 test/constraint/pt_plane_distance/reference.png create mode 100644 test/constraint/pt_plane_distance/reference.slvs create mode 100644 test/constraint/pt_plane_distance/reference_v20.slvs create mode 100644 test/constraint/pt_plane_distance/reference_v22.slvs create mode 100644 test/constraint/pt_plane_distance/test.cpp create mode 100644 test/constraint/pt_pt_distance/free_in_3d.png create mode 100644 test/constraint/pt_pt_distance/free_in_3d.slvs create mode 100644 test/constraint/pt_pt_distance/free_in_3d_v20.slvs create mode 100644 test/constraint/pt_pt_distance/free_in_3d_v22.slvs create mode 100644 test/constraint/pt_pt_distance/normal.png create mode 100644 test/constraint/pt_pt_distance/normal.slvs create mode 100644 test/constraint/pt_pt_distance/normal_v20.slvs create mode 100644 test/constraint/pt_pt_distance/normal_v22.slvs create mode 100644 test/constraint/pt_pt_distance/reference.png create mode 100644 test/constraint/pt_pt_distance/reference.slvs create mode 100644 test/constraint/pt_pt_distance/reference_v20.slvs create mode 100644 test/constraint/pt_pt_distance/reference_v22.slvs create mode 100644 test/constraint/pt_pt_distance/test.cpp create mode 100644 test/constraint/same_orientation/normal.png create mode 100644 test/constraint/same_orientation/normal.slvs create mode 100644 test/constraint/same_orientation/normal_v20.slvs create mode 100644 test/constraint/same_orientation/normal_v22.slvs create mode 100644 test/constraint/same_orientation/same_group.png create mode 100644 test/constraint/same_orientation/same_group.slvs create mode 100644 test/constraint/same_orientation/test.cpp create mode 100644 test/constraint/symmetric/free_in_3d.png create mode 100644 test/constraint/symmetric/free_in_3d.slvs create mode 100644 test/constraint/symmetric/free_in_3d_v20.slvs create mode 100644 test/constraint/symmetric/free_in_3d_v22.slvs create mode 100644 test/constraint/symmetric/normal.png create mode 100644 test/constraint/symmetric/normal.slvs create mode 100644 test/constraint/symmetric/normal_v20.slvs create mode 100644 test/constraint/symmetric/normal_v22.slvs create mode 100644 test/constraint/symmetric/test.cpp create mode 100644 test/constraint/symmetric_horiz/normal.png create mode 100644 test/constraint/symmetric_horiz/normal.slvs create mode 100644 test/constraint/symmetric_horiz/normal_v20.slvs create mode 100644 test/constraint/symmetric_horiz/normal_v22.slvs create mode 100644 test/constraint/symmetric_horiz/test.cpp create mode 100644 test/constraint/symmetric_line/normal.png create mode 100644 test/constraint/symmetric_line/normal.slvs create mode 100644 test/constraint/symmetric_line/normal_v20.slvs create mode 100644 test/constraint/symmetric_line/normal_v22.slvs create mode 100644 test/constraint/symmetric_line/test.cpp create mode 100644 test/constraint/symmetric_vert/normal.png create mode 100644 test/constraint/symmetric_vert/normal.slvs create mode 100644 test/constraint/symmetric_vert/normal_v20.slvs create mode 100644 test/constraint/symmetric_vert/normal_v22.slvs create mode 100644 test/constraint/symmetric_vert/test.cpp create mode 100644 test/constraint/vertical/line.png create mode 100644 test/constraint/vertical/line.slvs create mode 100644 test/constraint/vertical/line_v20.slvs create mode 100644 test/constraint/vertical/line_v22.slvs create mode 100644 test/constraint/vertical/pt_pt.png create mode 100644 test/constraint/vertical/pt_pt.slvs create mode 100644 test/constraint/vertical/pt_pt_v20.slvs create mode 100644 test/constraint/vertical/pt_pt_v22.slvs create mode 100644 test/constraint/vertical/test.cpp create mode 100644 test/constraint/where_dragged/free_in_3d.png create mode 100644 test/constraint/where_dragged/free_in_3d.slvs create mode 100644 test/constraint/where_dragged/free_in_3d_v20.slvs create mode 100644 test/constraint/where_dragged/free_in_3d_v22.slvs create mode 100644 test/constraint/where_dragged/normal.png create mode 100644 test/constraint/where_dragged/normal.slvs create mode 100644 test/constraint/where_dragged/normal_v20.slvs create mode 100644 test/constraint/where_dragged/normal_v22.slvs create mode 100644 test/constraint/where_dragged/test.cpp create mode 100644 test/core/expr/test.cpp create mode 100644 test/core/locale/test.cpp create mode 100644 test/core/path/test.cpp create mode 100644 test/debugtool.cpp create mode 100644 test/group/link/normal.png create mode 100644 test/group/link/normal.slvs create mode 100644 test/group/link/normal_v20.slvs create mode 100644 test/group/link/normal_v22.slvs create mode 100644 test/group/link/rect_v20.slvs create mode 100644 test/group/link/test.cpp create mode 100644 test/group/translate_asy/normal.png create mode 100644 test/group/translate_asy/normal.slvs create mode 100644 test/group/translate_asy/normal_v22.slvs create mode 100644 test/group/translate_asy/test.cpp create mode 100644 test/group/translate_nd/normal.png create mode 100644 test/group/translate_nd/normal.slvs create mode 100644 test/group/translate_nd/normal_v22.slvs create mode 100644 test/group/translate_nd/test.cpp create mode 100644 test/harness.cpp create mode 100644 test/harness.h create mode 100644 test/request/arc_of_circle/normal.png create mode 100644 test/request/arc_of_circle/normal.slvs create mode 100644 test/request/arc_of_circle/normal_v20.slvs create mode 100644 test/request/arc_of_circle/normal_v22.slvs create mode 100644 test/request/arc_of_circle/test.cpp create mode 100644 test/request/circle/free_in_3d.png create mode 100644 test/request/circle/free_in_3d.slvs create mode 100644 test/request/circle/free_in_3d_dof.png create mode 100644 test/request/circle/free_in_3d_v20.slvs create mode 100644 test/request/circle/free_in_3d_v22.slvs create mode 100644 test/request/circle/normal.png create mode 100644 test/request/circle/normal.slvs create mode 100644 test/request/circle/normal_dof.png create mode 100644 test/request/circle/normal_v20.slvs create mode 100644 test/request/circle/normal_v22.slvs create mode 100644 test/request/circle/test.cpp create mode 100644 test/request/cubic/normal.png create mode 100644 test/request/cubic/normal.slvs create mode 100644 test/request/cubic/normal_v20.slvs create mode 100644 test/request/cubic/normal_v22.slvs create mode 100644 test/request/cubic/test.cpp create mode 100644 test/request/cubic_periodic/normal.png create mode 100644 test/request/cubic_periodic/normal.slvs create mode 100644 test/request/cubic_periodic/normal_v20.slvs create mode 100644 test/request/cubic_periodic/normal_v22.slvs create mode 100644 test/request/cubic_periodic/test.cpp create mode 100644 test/request/datum_point/normal.png create mode 100644 test/request/datum_point/normal.slvs create mode 100644 test/request/datum_point/normal_v20.slvs create mode 100644 test/request/datum_point/normal_v22.slvs create mode 100644 test/request/datum_point/test.cpp create mode 100644 test/request/image/drawing.png create mode 100644 test/request/image/linked.slvs create mode 100644 test/request/image/normal.slvs create mode 100644 test/request/image/test.cpp create mode 100644 test/request/line_segment/normal.png create mode 100644 test/request/line_segment/normal.slvs create mode 100644 test/request/line_segment/normal_v20.slvs create mode 100644 test/request/line_segment/normal_v22.slvs create mode 100644 test/request/line_segment/test.cpp create mode 100644 test/request/ttf_text/normal.png create mode 100644 test/request/ttf_text/normal.slvs create mode 100644 test/request/ttf_text/normal_v20.slvs create mode 100644 test/request/ttf_text/normal_v22.slvs create mode 100644 test/request/ttf_text/test.cpp create mode 100644 test/request/workplane/normal.png create mode 100644 test/request/workplane/normal.slvs create mode 100644 test/request/workplane/normal_v20.slvs create mode 100644 test/request/workplane/normal_v22.slvs create mode 100644 test/request/workplane/test.cpp delete mode 100644 tools/CMakeLists.txt delete mode 100644 tools/lff2c.cpp delete mode 100644 tools/png2c.cpp delete mode 100644 tools/unifont2c.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5cb786f --- /dev/null +++ b/.clang-format @@ -0,0 +1,41 @@ +--- +# Things explicitly enumerated in CONTRIBUTING.md +IndentWidth: 4 +UseTab: Never +SpaceBeforeParens: Never +BreakBeforeBraces: Attach +ColumnLimit: 100 +# Docs say 100, but some places look more like 80. +#ColumnLimit: 80 + +# Based on minimizing diff when applying clang-format +AccessModifierOffset: -4 +AlignConsecutiveAssignments: true +AlignEscapedNewlines: DontAlign +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortLoopsOnASingleLine: false +AlwaysBreakTemplateDeclarations: Yes # MultiLine +BreakConstructorInitializers: BeforeColon +DerivePointerAlignment: true +FixNamespaceComments: true +IndentPPDirectives: AfterHash +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: Inner +SpaceAfterTemplateKeyword: false + +# No way to remove all space around operators as seen in much of the code I looked at. +#SpaceBeforeAssignmentOperators: false + +# Only seen some of the time (mostly just variables, not functions) +# but clang-format only has a true/false +#AlignConsecutiveDeclarations: true + + +# Would be nice to turn on eventually, maybe? +SortIncludes: false +SortUsingDeclarations: false + +# Hard to tell what the desired config here was. +# AllowShortFunctionsOnASingleLine: Inline +AllowShortFunctionsOnASingleLine: None diff --git a/.travis.yml b/.travis.yml index b51c80c..0681bf2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,113 @@ +os: linux +dist: xenial language: c -os: - - linux - - osx -sudo: required -dist: trusty -install: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./.travis/install-debian.sh; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./.travis/install-macos.sh; fi -script: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./.travis/build-debian.sh; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./.travis/build-macos.sh; fi -deploy: - - provider: releases - api_key: - secure: dDlkIawHcODlW9B/20/cQCtzeoocvs0hKuNngRKXKqzXLWTRq33oq/B7+39tAixWbmv6exTpijiKrRNFiSCW5Z4iwHLwaRD4XJznxw63e/Hus/dxg2Tvqx7XFpkCz8mT1Z+gZQE5YxAngeZPpI/sZbZtF1UO3yH5eLeeokZ15p26ZskQUPoYuzrTgTzYL3XfpG3F+20rNBawH1ycsCTVD/08/n31d2m3CrKAsbW7er92ek6w4fzKr7NW8WeXjrPJETVpw5fQg1Od3pRGW8dPQaJcvKQEogMp8Mm0ETYd0qigg89/giBz7QwOgmAWQ4dH+DfZH4Ojl//127QztBolMvyDMQBykWrtJoGcij05sT6K2IJr2FHeUBO12MAEdjiVvhQj3DtTzjPiZAHHDBSLWxLKWWhlhHE4pq7g1MQhqXkaAHI2BLNzwLmaowbMT0bECf9yfz6xx18h6XPQFX44oOktraobVALFlyHqeKa8zdcUt22LF6uAL1m5dxL0tny3eXCIPE4UH/RZgua/cHV9G3cUvKQa/QnFSLRhvWVSbGB+7YsHouBJcsUOOW1gmd5442XuC7mpppccRldh+GSxUk6TBJRAx7TeQ0ybDUaoco9MUqp2twv3KreR2+8Q12PDaAhfQVNEGdF3wTm1sShImjCN4VN3eSLlBEbve1QRQXM= - skip_cleanup: true - file: build/solvespace.dmg - on: - repo: solvespace/solvespace - tags: true - condition: "$TRAVIS_OS_NAME == osx" +git: + submodules: false +if: tag != edge +stages: + - test + - name: clean + if: (NOT type IN (pull_request)) AND (branch = master) + - name: deploy + if: (NOT type IN (pull_request)) AND (branch = master OR tag IS present) +jobs: + include: + - stage: clean + name: Remove Github Release + os: linux + dist: bionic + script: .travis/remove-edge.sh + - stage: test + name: macOS + os: osx + osx_image: xcode12.2 + install: ".travis/install-macos.sh" + script: ".travis/build-macos.sh" + - stage: deploy + name: macOS + os: osx + osx_image: xcode12.2 + install: ".travis/install-macos.sh" + script: ".travis/build-macos.sh release && .travis/sign-macos.sh" + before_deploy: source .travis/tag-edge.sh + deploy: + provider: releases + skip_cleanup: true + prerelease: true + overwrite: true + edge: true + name: ${TRAVIS_TAG:-edge} + release_notes: $TRAVIS_COMMIT_MESSAGE + file: build/bin/SolveSpace.dmg + on: + all_branches: true + - stage: test + name: "Ubuntu" + os: linux + dist: bionic + install: .travis/install-ubuntu.sh + script: .travis/build-ubuntu.sh + - stage: test + name: "Windows" + os: windows + install: .travis/install-windows.sh + script: .travis/build-windows.sh + - stage: deploy + name: "Windows" + os: windows + install: .travis/install-windows.sh + script: .travis/build-windows.sh release + before_deploy: source .travis/tag-edge.sh + deploy: + provider: releases + skip_cleanup: true + prerelease: true + overwrite: true + edge: true + name: ${TRAVIS_TAG:-edge} + release_notes: $TRAVIS_COMMIT_MESSAGE + file: build/bin/RelWithDebInfo/solvespace.exe + on: + all_branches: true + - stage: deploy + name: "Windows with OpenMP" + os: windows + install: .travis/install-windows.sh + script: .travis/build-windows.sh release openmp + before_deploy: source .travis/tag-edge.sh + deploy: + provider: releases + skip_cleanup: true + prerelease: true + overwrite: true + edge: true + name: ${TRAVIS_TAG:-edge} + release_notes: $TRAVIS_COMMIT_MESSAGE + file: build/bin/RelWithDebInfo/solvespace-openmp.exe + on: + all_branches: true + - &deploy-snap + stage: deploy + name: Snap amd64 + os: linux + arch: amd64 + dist: bionic + addons: + snaps: + - name: snapcraft + confinement: classic + script: .travis/build-snap.sh + deploy: + - provider: script + script: sudo .travis/deploy-snap.sh edge + skip_cleanup: true + - provider: script + script: sudo .travis/deploy-snap.sh edge,beta + skip_cleanup: true + on: + tags: true + - <<: *deploy-snap + name: Snap arm64 + arch: arm64-graviton2 + group: edge + virt: lxd diff --git a/CHANGELOG.md b/CHANGELOG.md index 095eee6..f3faa47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,150 @@ Changelog ========= +3.0 +--- + +New sketch features: + * New intersection boolean operation for solid models. + * New groups, revolution and helical extrusion. + * Extrude, lathe, translate and rotate groups can use the "assembly" + boolean operation, to increase performance. + * The solid model of extrude and lathe groups can be suppressed, + for splitting a single model in multiple parts to export, + or if only the generated entities are desired, without the mesh. + * Translate and rotate groups can create n-dimensional arrays using + the "difference" and "assembly" boolean operations. + * A new sketch in workplane group can be created based on existing workplane. + * TTF text request has two additional points on the right side, which allow + constraining the width of text. + * Image requests can now be created, similar to TTF text requests. + This replaces the "style → background image" feature. + * Irrelevant points (e.g. arc center point) are not counted when estimating + the bounding box used to compute chord tolerance. + * When adding a constraint which has a label and is redundant with another + constraint, the constraint is added as a reference, avoiding an error. + * Datum points can be copied and pasted. + * "Split Curves at Intersection" can now split curves at point lying on curve, + not just at intersection of two curves. + * Property browser now shows amount of degrees of freedom in group list. + +New constraint features: + * When dragging an arc or rectangle point, it will be automatically + constrained to other points with a click. + * When selecting a constraint, the requests it constraints can be selected + in the text window. + * When selecting an entity, the constraints applied to it can be selected + in the text window. + * Distance constraint labels can now be formatted to use SI prefixes. + Values are edited in the configured unit regardless of label format. + * When creating a constraint, if an exactly identical constraint already + exists, it is now selected instead of adding a redundant constraint. + * It is now possible to turn off automatic creation of horizontal/vertical + constraints on line segments. + * Automatic creation of constraints no longer happens if the constraint + would have been redundant with other ones. + * New option to open the constraint editor for newly created constraints + with a value. + +New export/import features: + * Link IDF circuit boards in an assembly (.emn files) + * Three.js: allow configuring projection for exported model, and initially + use the current viewport projection. + * Wavefront OBJ: a material file is exported alongside the model, containing + mesh color information. + * DXF/DWG: 3D DXF files are imported as construction entities, in 3d. + * Q3D: [Q3D](https://github.com/q3k/q3d/) triangle meshes can now be + exported. This format allows to easily hack on triangle mesh data created + in SolveSpace, supports colour information and is more space efficient than + most other formats. + * VRML (WRL) triangle meshes can now be exported, useful for e.g. [KiCAD](http://kicad.org). + * Export 2d section: custom styled entities that lie in the same + plane as the exported section are included. + +New rendering features: + * The "Show/hide hidden lines" button is now a tri-state button that allows + showing all lines (on top of shaded mesh), stippling occluded lines + or not drawing them at all. + * The "Show/hide outlines" button is now independent from "Show/hide edges". + +New measurement/analysis features: + * New choice for base unit, meters. + * New command for measuring total length of selected entities, + "Analyze → Measure Perimeter". + * New command for measuring center of mass, with live updates as the sketch + changes, "Analyze → Center of Mass". + * New option for displaying areas of closed contours. + * When calculating volume of the mesh, volume of the solid from the current + group is now shown alongside total volume of all solids. + * When calculating area, and faces are selected, calculate area of those faces + instead of the closed contour in the sketch. + * When selecting a point and a line, projected distance to current + workplane is displayed. + +Other new features: + * Added ExportBackgroundColor in configuration for EPS, PDF, and SVG files. + * Improvements to the text window for selected entities and constraints. + * Ambient light source added in text window to allow flat shaded renderings. + * New command-line interface, for batch exporting and more. + * The graphical interface now supports HiDPI screens on every OS. + * New option to lock Z axis to be always vertical, like in SketchUp. + * New button to hide all construction entities. + * New link to match the on-screen size of the sketch with its actual size, + "view → set to full scale". + * When zooming to fit, constraints are also considered. + * Ctrl-clicking entities now deselects them, as the inverse of clicking. + * When clicking on an entity that shares a place with other entities, + the entity from the current group is selected. + * When dragging an entity that shares a place with other entities, + the entity from a request is selected. For example, dragging a point on + a face of an extrusion coincident with the source sketch plane will + drag the point from the source sketch. + * The default font for TTF text is now Bitstream Vera Sans, which is + included in the resources such that it is available on any OS. + * In expressions, numbers can contain the digit group separator, "_". + * The "=" key is bound to "Zoom In", like "+" key. + * The numpad decimal separator key is bound to "." regardless of locale. + * On Windows, full-screen mode is implemented. + * On Linux, native file chooser dialog can be used. + * New edit menu items "Line Styles", "View Projection" and "Configuration" + that are shortcuts to the respective configuration screens. + * New cmake build options using -DENABLE_OPENMP=yes and -DENABLE_LTO=yes + to enable support for multi-threading and link-time optimization. + +Bugs fixed: + * Fixed broken --view options for command line thumbnail image creation. + * Some errors in Triangulation of surfaces. + * Some NURNS boolean operations that failed particularly on surfaces + created with Lathe, Revolve, or Helix. + * Segfault in Remove Spline Point context menu. + * A point in 3d constrained to any line whose length is free no longer + causes the line length to collapse. + * Curve-line constraints (in 3d), parallel constraints (in 3d), and + same orientation constraints are more robust. + * Adding some constraints (vertical, midpoint, etc) twice errors out + immediately, instead of later and in a confusing way. + * Constraining a newly placed point to a hovered entity does not cause + spurious changes in the sketch. + * Points highlighted with "Analyze → Show Degrees of Freedom" are drawn + on top of all other geometry. + * A step rotate/translate group using a group forced to triangle mesh + as a source group also gets forced to triangle mesh. + * Paste Transformed with a negative scale does not invert arcs. + * The tangent arc now modifies the original entities instead of deleting + them, such that their constraints are retained. + * When linking a sketch file, missing custom styles are now imported from + the linked file. + * 3Dconnexion SpaceMouse should now work (on Windows and macOS X). + * Improved NURBS boolean operations on curved surfaces in some cases. + * Show only usable fonts in the font selector. + +2.x +--- + +Bug fixes: + * Do not crash when changing an unconstrained lathe group between + union and difference modes. + 2.3 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index ea464e5..ad23d98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,20 +1,36 @@ # cmake configuration -cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) -cmake_policy(VERSION 3.1.0) +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message(FATAL_ERROR + "In-tree builds are not supported; please perform an out-of-tree build:\n" + " rm -rf CMakeCache.txt CMakeFiles/\n" + " mkdir build && cd build && cmake ..") +endif() + +cmake_minimum_required(VERSION 3.7.2 FATAL_ERROR) +if(NOT CMAKE_VERSION VERSION_LESS 3.11.0) + cmake_policy(VERSION 3.11.0) +endif() +if(NOT CMAKE_VERSION VERSION_LESS 3.9) + # LTO/IPO with non-Intel compilers on Linux requires policy CMP0069 to be set to NEW. + # Set it explicitly until cmake_minimum_required is raised to >= 3.9. + cmake_policy(SET CMP0069 NEW) +endif() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED YES) -include(CheckIncludeFile) - # for /MT on MSVC set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_SOURCE_DIR}/cmake/c_flag_overrides.cmake") set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX "${CMAKE_SOURCE_DIR}/cmake/cxx_flag_overrides.cmake") +if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") +endif() + # project # NOTE TO PACKAGERS: The embedded git commit hash is critical for rapid bug triage when the builds @@ -25,42 +41,52 @@ include(GetGitCommitHash) # set(GIT_COMMIT_HASH 0000000000000000000000000000000000000000) project(solvespace) -set(solvespace_VERSION_MAJOR 2) -set(solvespace_VERSION_MINOR 3) +set(solvespace_VERSION_MAJOR 3) +set(solvespace_VERSION_MINOR 0) string(SUBSTRING "${GIT_COMMIT_HASH}" 0 8 solvespace_GIT_HASH) -if(NOT WIN32 AND NOT APPLE) - set(GUI gtk2 CACHE STRING "GUI toolkit to use (one of: gtk2 gtk3)") +set(ENABLE_GUI ON CACHE BOOL + "Whether the graphical interface is enabled") +set(ENABLE_CLI ON CACHE BOOL + "Whether the command line interface is enabled") +set(ENABLE_TESTS ON CACHE BOOL + "Whether the test suite will be built and run") +set(ENABLE_COVERAGE OFF CACHE BOOL + "Whether code coverage information will be collected") +set(ENABLE_SANITIZERS OFF CACHE BOOL + "Whether to enable Clang's AddressSanitizer and UndefinedBehaviorSanitizer") +set(ENABLE_OPENMP OFF CACHE BOOL + "Whether geometric operations will be parallelized using OpenMP") +set(ENABLE_LTO OFF CACHE BOOL + "Whether interprocedural (global) optimizations are enabled") + +set(OPENGL 3 CACHE STRING "OpenGL version to use (one of: 1 3)") + +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) +set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) +if("${CMAKE_GENERATOR}" STREQUAL "Xcode") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_BINARY_DIR}/bin>) endif() -# compiler - -if(WIN32) - add_definitions( - -D_CRT_SECURE_NO_DEPRECATE - -D_CRT_SECURE_NO_WARNINGS - -D_SCL_SECURE_NO_WARNINGS - -D_WIN32_WINNT=0x500 - -D_WIN32_IE=_WIN32_WINNT - -DISOLATION_AWARE_ENABLED - -DWIN32 - -DWIN32_LEAN_AND_MEAN - -DUNICODE - -DNOMINMAX - -D_UNICODE) +if(NOT CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID) + message(FATAL_ERROR "C and C++ compilers should be supplied by the same vendor") endif() -if(MSVC) - # Many versions of MSVC do not have the (C99) inline keyword, instead - # they have their own __inline; this breaks `static inline` functions. - # We do not want to care and so we fix this with a definition. - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Dinline=__inline") +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) + # GCC 4.8/4.9 ship with broken but present . meh. + message(FATAL_ERROR "GCC 5.0+ is required") + endif() endif() -if(CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) - set(WARNING_FLAGS "-Wall -Wextra -Wno-unused-parameter") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ${WARNING_FLAGS}") +# common compiler flags +include(CheckCXXCompilerFlag) + +set(FILE_PREFIX_MAP "-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.") +check_cxx_compiler_flag("${FILE_PREFIX_MAP}" HAS_FILE_PREFIX_MAP) +if(HAS_FILE_PREFIX_MAP) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FILE_PREFIX_MAP}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FILE_PREFIX_MAP}") endif() if(MINGW) @@ -68,128 +94,316 @@ if(MINGW) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++") endif() -if(APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") +# Ensure that all platforms use 64-bit IEEE floating point operations for consistency; +# this is most important for the testsuite, which compares savefiles directly +# and depends on consistent rounding of intermediate results. +if(CMAKE_SYSTEM_PROCESSOR STREQUAL "i686" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "X86") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(FLOAT_FLAGS "-mfpmath=sse -msse2") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(FLOAT_FLAGS "-msse2") + endif() + + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FLOAT_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FLOAT_FLAGS}") endif() -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_EXE_LINKER_FLAGS}") +if(ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported() + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) endif() -if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") +if(ENABLE_OPENMP) + find_package( OpenMP REQUIRED ) + include(FindOpenMP) + if(OPENMP_FOUND) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + message(STATUS "found OpenMP, compiling with flags: " ${OpenMP_CXX_FLAGS} ) + endif() +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") set(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed ${CMAKE_EXE_LINKER_FLAGS}") endif() -if(SANITIZE) - if(NOT (CMAKE_C_COMPILER_ID STREQUAL Clang AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)) - message(ERROR "Sanitizers are only available in Clang/Clang++") +if(ENABLE_SANITIZERS) + if(NOT SANITIZERS) + set(SANITIZERS "address;undefined") + endif() + + if("thread" IN_LIST SANITIZERS) + list(REMOVE_ITEM SANITIZERS "thread") + list(APPEND SANITIZE_OPTIONS thread) endif() - set(SANITIZE_FLAGS "-O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls") - set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fsanitize=address,undefined,integer") + if("address" IN_LIST SANITIZERS) + list(REMOVE_ITEM SANITIZERS "address") + list(APPEND SANITIZE_OPTIONS address) + endif() + if("undefined" IN_LIST SANITIZERS) + list(REMOVE_ITEM SANITIZERS "undefined") + list(APPEND SANITIZE_OPTIONS alignment bounds) + list(APPEND SANITIZE_OPTIONS shift signed-integer-overflow integer-divide-by-zero) + list(APPEND SANITIZE_OPTIONS null bool enum) + list(APPEND SANITIZE_OPTIONS return) + endif() + if(SANITIZERS) + message(FATAL_ERROR "Unknown sanitizer(s) ${SANITIZERS}") + else() + message(STATUS "Using sanitizer options ${SANITIZE_OPTIONS}") + endif() + + string(REPLACE ";" "," SANITIZE_OPTIONS "${SANITIZE_OPTIONS}") + + if (NOT APPLE) + set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS} -fno-sanitize-recover=address,undefined") + else() + set(SANITIZE_FLAGS "-O1 -fsanitize=${SANITIZE_OPTIONS}") + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(SANITIZE_FLAGS "${SANITIZE_FLAGS} -fuse-ld=gold") + else() + message(FATAL_ERROR "Sanitizers are only available when using GCC or Clang") + endif() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZE_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZE_FLAGS}") endif() -# dependencies +# common dependencies -find_package(OpenGL REQUIRED) +if(APPLE) + set(CMAKE_FIND_FRAMEWORK LAST) +endif() message(STATUS "Using in-tree libdxfrw") add_subdirectory(extlib/libdxfrw) -if(WIN32) - # Configure Freetype first. If done later, it will notice that - # zlib is available, try to use it and promptly break on MSVC - # in a very obscure way. Given that the only use of zlib, bzip2 - # and png support is in support for extremely obsolete Unix fonts, - # we don't care. - find_package(Freetype) +message(STATUS "Using in-tree flatbuffers") +set(FLATBUFFERS_BUILD_FLATLIB ON CACHE BOOL "") +set(FLATBUFFERS_BUILD_FLATC ON CACHE BOOL "") +set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "") +set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "") +add_subdirectory(extlib/flatbuffers EXCLUDE_FROM_ALL) + +message(STATUS "Using in-tree q3d") +add_subdirectory(extlib/q3d) +set(Q3D_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extlib/q3d) + +message(STATUS "Using in-tree mimalloc") +set(MI_OVERRIDE OFF CACHE BOOL "") +set(MI_BUILD_SHARED OFF CACHE BOOL "") +set(MI_BUILD_OBJECT OFF CACHE BOOL "") +set(MI_BUILD_TESTS OFF CACHE BOOL "") +add_subdirectory(extlib/mimalloc EXCLUDE_FROM_ALL) +set(MIMALLOC_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/mimalloc/include) + +if(WIN32 OR APPLE) + # On Win32 and macOS we use vendored packages, since there is little to no benefit + # to trying to find system versions. In particular, trying to link to libraries from + # Homebrew or macOS system libraries into the .app file is highly likely to result + # in incompatibilities after upgrades. + + include(FindVendoredPackage) + include(AddVendoredSubdirectory) + + set(FORCE_VENDORED_ZLIB ON) + set(FORCE_VENDORED_PNG ON) + set(FORCE_VENDORED_Freetype ON) + + find_vendored_package(ZLIB zlib + ZLIB_LIBRARY zlibstatic + ZLIB_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/zlib) + list(APPEND ZLIB_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extlib/zlib) + + find_vendored_package(PNG libpng + SKIP_INSTALL_ALL ON + PNG_LIBRARY png_static + PNG_PNG_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/libpng) + list(APPEND PNG_PNG_INCLUDE_DIR ${CMAKE_BINARY_DIR}/extlib/libpng) + + find_vendored_package(Freetype freetype + WITH_ZLIB OFF + WITH_BZip2 OFF + WITH_PNG OFF + WITH_HarfBuzz OFF + FREETYPE_LIBRARY freetype + FREETYPE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/freetype/include) + + message(STATUS "Using in-tree pixman") + add_vendored_subdirectory(extlib/pixman) + set(PIXMAN_FOUND YES) + set(PIXMAN_LIBRARY pixman) + set(PIXMAN_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/pixman/pixman) + list(APPEND PIXMAN_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/extlib/pixman/pixman) + + message(STATUS "Using in-tree cairo") + add_vendored_subdirectory(extlib/cairo) + set(CAIRO_FOUND YES) + set(CAIRO_LIBRARIES cairo) + set(CAIRO_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extlib/cairo/src) + list(APPEND CAIRO_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/extlib/cairo/src) +else() + # On Linux and BSDs we're a good citizen and link to system libraries. + find_package(Backtrace) + find_package(PkgConfig REQUIRED) + find_package(ZLIB REQUIRED) + find_package(PNG REQUIRED) + find_package(Freetype REQUIRED) + pkg_check_modules(CAIRO REQUIRED cairo) +endif() + +# GUI dependencies + +if(ENABLE_GUI) + if(WIN32) + if(OPENGL STREQUAL "3") + message(STATUS "Using in-tree ANGLE") + set(ANGLE_STATIC ON CACHE INTERNAL "") + set(ANGLE_ENABLE_D3D9 ON CACHE INTERNAL "") + set(ANGLE_ENABLE_D3D11 ON CACHE INTERNAL "") + set(ANGLE_ENABLE_OPENGL ON CACHE INTERNAL "") + set(ANGLE_ENABLE_ESSL ON CACHE INTERNAL "") + set(ANGLE_ENABLE_GLSL ON CACHE INTERNAL "") + set(ANGLE_ENABLE_HLSL ON CACHE INTERNAL "") + add_vendored_subdirectory(extlib/angle) + set(OPENGL_LIBRARIES EGL GLESv2) + set(OPENGL_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/angle/include) + else() + find_package(OpenGL REQUIRED) + endif() + + if(MSVC AND ${CMAKE_SIZEOF_VOID_P} EQUAL 4) + message(STATUS "Using prebuilt SpaceWare") + set(SPACEWARE_FOUND TRUE) + set(SPACEWARE_INCLUDE_DIR + "${CMAKE_SOURCE_DIR}/extlib/si") + set(SPACEWARE_LIBRARIES + "${CMAKE_SOURCE_DIR}/extlib/si/siapp.lib") + endif() + elseif(APPLE) + find_package(OpenGL REQUIRED) + find_library(APPKIT_LIBRARY AppKit REQUIRED) + else() + find_package(OpenGL REQUIRED) + find_package(SpaceWare) + pkg_check_modules(FONTCONFIG REQUIRED fontconfig) + pkg_check_modules(JSONC REQUIRED json-c) + pkg_check_modules(GTKMM REQUIRED gtkmm-3.0>=3.18 pangomm-1.4 x11) + endif() +endif() - if(NOT FREETYPE_FOUND) - message(STATUS "Using in-tree libfreetype") +# code coverage - add_subdirectory(extlib/libfreetype EXCLUDE_FROM_ALL) +if(ENABLE_COVERAGE) + if(CMAKE_CXX_COMPILER_ID STREQUAL GNU) + find_program(GCOV gcov) + elseif(CMAKE_CXX_COMPILER_ID MATCHES Clang) + find_program(LLVM_COV llvm-cov) - set(FREETYPE_LIBRARY - freetype) - set(FREETYPE_INCLUDE_DIRS - "${CMAKE_SOURCE_DIR}/extlib/libfreetype/include") - find_package(Freetype REQUIRED) + if(LLVM_COV) + set(GCOV ${CMAKE_CURRENT_BINARY_DIR}/llvm-gcov.sh) + file(WRITE ${GCOV} "#!/bin/sh -e\n${LLVM_COV} gcov $*") + execute_process(COMMAND chmod +x ${GCOV}) + endif() endif() - find_package(ZLIB) + find_program(LCOV lcov) + find_program(GENHTML genhtml) + if(NOT GCOV OR NOT LCOV OR NOT GENHTML) + message(FATAL_ERROR "gcov/llvm-cov and lcov are required for producing coverage reports") + endif() +endif() - if(NOT ZLIB_FOUND) - message(STATUS "Using in-tree zlib") - add_subdirectory(extlib/zlib EXCLUDE_FROM_ALL) +# translations - message(STATUS "Using in-tree libpng") - set(ZLIB_LIBRARY - zlibstatic) - set(ZLIB_INCLUDE_DIR - "${CMAKE_SOURCE_DIR}/extlib/zlib" - "${CMAKE_BINARY_DIR}/extlib/zlib") - find_package(ZLIB REQUIRED) - endif() +find_program(XGETTEXT xgettext) +find_program(MSGINIT msginit) +find_program(MSGMERGE msgmerge) +if(XGETTEXT AND MSGINIT AND MSGMERGE) + set(HAVE_GETTEXT TRUE) +else() + message(WARNING "Gettext not found, translations will not be updated") + set(HAVE_GETTEXT FALSE) +endif() - find_package(PNG) +# solvespace-only compiler flags - if(NOT PNG_FOUND) - message(STATUS "Using in-tree libpng") +if(WIN32) + add_definitions( + -D_CRT_SECURE_NO_DEPRECATE + -D_CRT_SECURE_NO_WARNINGS + -D_SCL_SECURE_NO_WARNINGS + -DWINVER=0x0501 + -D_WIN32_WINNT=0x0501 + -D_WIN32_IE=_WIN32_WINNT + -DISOLATION_AWARE_ENABLED + -DWIN32 + -DWIN32_LEAN_AND_MEAN + -DUNICODE + -D_UNICODE + -DNOMINMAX + -D_USE_MATH_DEFINES) +endif() - set(SKIP_INSTALL_ALL - ON) - add_subdirectory(extlib/libpng EXCLUDE_FROM_ALL) +if(MSVC) + # Many versions of MSVC do not have the (C99) inline keyword, instead + # they have their own __inline; this breaks `static inline` functions. + # We do not want to care and so we fix this with a definition. + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Dinline=__inline") + # Same for the (C99) __func__ special variable; we use it only in C++ code. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D__func__=__FUNCTION__") - set(PNG_LIBRARY - png16_static) - set(PNG_PNG_INCLUDE_DIR - "${CMAKE_SOURCE_DIR}/extlib/libpng" - "${CMAKE_BINARY_DIR}/extlib/libpng") - find_package(PNG REQUIRED) - endif() + # We rely on these /we flags. They correspond to the GNU-style flags below as + # follows: /w4062=-Wswitch + set(WARNING_FLAGS "${WARNING_FLAGS} /we4062") +endif() - if(NOT MINGW) - message(STATUS "Using prebuilt SpaceWare") - set(SPACEWARE_FOUND TRUE) - set(SPACEWARE_INCLUDE_DIR - "${CMAKE_SOURCE_DIR}/extlib/si") - set(SPACEWARE_LIBRARIES - "${CMAKE_SOURCE_DIR}/extlib/si/siapp.lib") +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(WARNING_FLAGS "-Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers") + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(WARNING_FLAGS "${WARNING_FLAGS} -Wfloat-conversion") endif() -elseif(APPLE) - set(CMAKE_FIND_FRAMEWORK LAST) + # We rely on these -Werror flags. + set(WARNING_FLAGS "${WARNING_FLAGS} -Werror=switch") +endif() - find_package(PNG REQUIRED) - find_package(Freetype REQUIRED) - find_library(APPKIT_LIBRARY AppKit REQUIRED) -else() # Linux and compatible systems - find_package(SpaceWare) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}") - # Use freedesktop's pkg-config to locate everything. - find_package(PkgConfig REQUIRED) - pkg_check_modules(ZLIB REQUIRED zlib) - pkg_check_modules(PNG REQUIRED libpng) - pkg_check_modules(FONTCONFIG REQUIRED fontconfig) - pkg_check_modules(JSONC REQUIRED json-c) - pkg_check_modules(GLEW REQUIRED glew) - pkg_check_modules(FREETYPE REQUIRED freetype2) - - set(HAVE_GTK TRUE) - if(GUI STREQUAL "gtk3") - set(HAVE_GTK3 TRUE) - pkg_check_modules(GTKMM REQUIRED gtkmm-3.0 pangomm-1.4 x11) - elseif(GUI STREQUAL "gtk2") - set(HAVE_GTK2 TRUE) - pkg_check_modules(GTKMM REQUIRED gtkmm-2.4 pangomm-1.4 x11) - else() - message(FATAL_ERROR "GUI unrecognized: ${GUI}") +if(WIN32) + set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -l0") +endif() + +if(ENABLE_COVERAGE) + if(NOT (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR + CMAKE_CXX_COMPILER_ID MATCHES "Clang")) + message(FATAL_ERROR "Code coverage is only available on GCC and Clang") + endif() + + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(FATAL_ERROR "Code coverage produces reliable results only on Debug builds") endif() + + # With -fexceptions, every call becomes a branch. While technically accurate, + # this is not useful for us. + set(COVERAGE_FLAGS -fno-exceptions --coverage) + set(COVERAGE_LIBRARY --coverage) endif() -# components +# application components -add_subdirectory(tools) +add_subdirectory(res) add_subdirectory(src) add_subdirectory(exposed) +if(ENABLE_TESTS) + add_subdirectory(test) +endif() +if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + add_subdirectory(bench) +else() + message(STATUS "Benchmarking disabled in debug builds.") +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4caf715 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,291 @@ +Contributing to SolveSpace +========================== + +Contributing bug reports +------------------------ + +Bug reports are always welcome! When reporting a bug, please include the following: + + * The version of SolveSpace (use Help → About...); + * The operating system; + * The save file that reproduces the incorrect behavior, or, if trivial or impossible, + instructions for reproducing it. + +GitHub does not allow attaching `*.slvs` files, but it does allow attaching `*.zip` files, +so any savefiles should first be archived. + +Signing the CLA +--------------- + +To contribute code, translations, artwork, or other resources to SolveSpace, it is necessary to +sign a [Contributor License Agreement](https://cla-assistant.io/solvespace/solvespace). + +Contributing translations +------------------------- + +To contribute a translation, not a lot is necessary—at a minimum, you need to be able +to edit .po files with a tool such as [poedit](https://poedit.net/). Once you have +such a tool installed, take `res/messages.pot` and start translating! + +However, if you want to see your translation in action, a little more work is necessary. +First, you need to be able to build SolveSpace; see [README](README.md). After that: + + * Copy `res/messages.pot` to `res/locales/xx_YY.po`, where `xx` is an ISO 639-1 + country code, and `YY` is an ISO 3166-1 language code. + * Add a line `xx-YY,LCID,Name` to `res/locales.txt`, where `xx-YY` have the same + meaning as above, `LCID` is a Windows Language Code Identifier ([MS-LCID][] + has a complete list), and `Name` is the full name of your locale in your language. + * Add `locales/xx_YY.po` in `res/CMakeLists.txt`—search for `locales/en_US.po` + to see where it should be added. + +You're done! Recompile SolveSpace and you should be able to select your translation +via Help → Language. + +[MS-LCID]: https://msdn.microsoft.com/en-us/library/cc233965.aspx + +Contributing code +----------------- + +SolveSpace is written in C++, and currently targets all compilers compliant with C++11. +This includes GCC 5 and later, Clang 3.3 and later, and Visual Studio 12 (2013) and later. + +### High-level conventions + +#### Portability + +SolveSpace aims to consist of two general parts: a fully portable core, and platform-specific +UI and support code. Anything outside of `src/platform/` should only use standard C++11, +and rely on `src/platform/unixutil.cpp` and `src/platform/w32util.cpp` to interact with +the OS where this cannot be done through the C++11 standard library. + +#### Libraries + +SolveSpace primarily relies on the C++11 STL. STL has well-known drawbacks, but is also +widely supported, used, and understood. SolveSpace also includes a fair amount of use of +bespoke containers List and IdList; these provide STL iterators, and can be used when +convenient, such as when reusing other code. + +One notable departure here is the STL I/O threads. SolveSpace does not use STL I/O threads +for two reasons: (i) the interface is borderline unusable, and (ii) on Windows it is not +possible to open files with Unicode paths through STL. + +When using external libraries (other than to access platform features), the libraries +should satisfy the following conditions: + + * Portable, and preferably not interacting with the platform at all; + * Can be included as a CMake subproject, to facilitate Windows, Android, etc. builds; + * Use a license less restrictive than GPL (BSD/MIT, Apache2, MPL, etc.) + +#### String encoding + +Internally, SolveSpace exclusively stores and uses UTF-8 for all purposes; any `std::string` +may be assumed to be encoded in UTF-8. On Windows, UTF-8 strings are converted to and from +wide strings at the boundary; see [UTF-8 Everywhere][utf8] for details. + +[utf8]: http://utf8everywhere.org/ + +#### String formatting + +For string formatting, a wrapper around `sprintf`, `ssprintf`, is used. A notable +pitfall when using it is trying to pass an `std::string` argument without first converting +it to a C string with `.c_str()`. + +#### Filesystem access + +For filesystem access, the C standard library is used. The `ssfopen` and `ssremove` +wrappers are provided that accept UTF-8 encoded paths. + +#### Assertions + +To ensure that internal invariants hold, the `ssassert` function is used, e.g. +`ssassert(!isFoo, "Unexpected foo condition");`. Unlike the standard `assert` function, +the `ssassert` function is always enabled, even in release builds. It is more valuable +to discover a bug through a crash than to silently generate incorrect results, and crashes +do not result in losing more than a few minutes of work thanks to the autosave feature. + +### Use of C++ features + +The conventions described in this section should be used for all new code, but there is a lot +of existing code in SolveSpace that does not use them. This is fine; don't touch it if it works, +but if you need to modify it anyway, might as well modernize it. + +#### Exceptions + +Exceptions are not used primarily because SolveSpace's testsuite uses measurement +of branch coverage, important for the critical parts such as the geometric kernel. +Every function call with exceptions enabled introduces a branch, making branch coverage +measurement useless. + +#### Operator overloading + +Operator overloading is not used primarily for historical reasons. Instead, method such +as `Plus` are used. + +#### Member visibility + +Member visibility is not used for implementation hiding. Every member field and function +is `public`. + +#### Constructors + +Constructors are not used for initialization, chiefly because indicating an error +in a constructor would require throwing an exception, nor does it use constructors for +blanket zero-initialization because of the performance impact of doing this for common +POD classes like `Vector`. + +Instances can be zero-initialized using the aggregate-initialization syntax, e.g. `Foo foo = {};`. +This zero-initializes the POD members and default-initializes the non-POD members, generally +being an equivalent of `memset(&foo, 0, sizeof(foo));` but compatible with STL containers. + +#### Input- and output-arguments + +Functions accepting an input argument take it either by-value (`Vector v`) or +by-const-reference (`const Vector &v`). Generally, passing by-value is safer as the value +cannot be aliased by something else, but passing by-const-reference is faster, as a copy is +eliminated. Small values should always be passed by-value, and otherwise functions that do not +capture pointers into their arguments should take them by-const-reference. Use your judgement. + +Functions accepting an output argument always take it by-pointer (`Vector *v`). This makes +it immediately visible at the call site as it is seen that the address is taken. Arguments +are never passed by-reference, except when needed for interoperability with STL, etc. + +#### Iteration + +`foreach`-style iteration is preferred for both STL and `List`/`IdList` containers as it indicates +intent clearly, as opposed to `for`-style. + +#### Const correctness + +Functions that do not mutate `this` should be marked as `const`; when iterating a collection +without mutating any of its elements, `for(const Foo &elem : collection)` is preferred to indicate +the intent. + +### Coding style + +Code is formatted by the following rules: + + * Code is indented using 4 spaces, with no trailing spaces, and lines are wrapped + at 100 columns; + * Braces are placed at the end of the line with the declaration or control flow statement; + * Braces are used with every control flow statement, even if there is only one statement + in the body; + * There is no space after control flow keywords (`if`, `while`, etc.); + * Identifiers are formatted in camel case; variables start with a lowercase letter + (`exampleVariable`) and functions start with an uppercase letter (`ExampleFunction`). + +For example: + +```c++ +std::string SolveSpace::Dirname(std::string filename) { + int slash = filename.rfind(PATH_SEP); + if(slash >= 0) { + return filename.substr(0, slash); + } + + return ""; +} +``` + +If you install [clang-format][], this style can be automatically applied by staging your changes +with `git add -u`, running `git clang-format`, and staging any changes it made again. + +[clang-format]: https://clang.llvm.org/docs/ClangFormat.html + +Debugging code +-------------- + +SolveSpace releases are thoroughly tested but sometimes they contain crash +bugs anyway. The reason for such crashes can be determined only if the executable +was built with debug information. + +### Debugging a released version + +The Linux distributions usually include separate debug information packages. +On a Debian derivative (e.g. Ubuntu), these can be installed with: + + apt-get install solvespace-dbg + +The macOS releases include the debug information, and no further action +is needed. + +The Windows releases include the debug information on the GitHub +[release downloads page](https://github.com/solvespace/solvespace/releases). + +### Debugging a custom build + +If you are building SolveSpace yourself on macOS, use the XCode +CMake generator, then open the project in XCode as usual, select +the Debug build scheme, and build the project: + + cd build + cmake .. -G Xcode [other cmake args...] + +If you are building SolveSpace yourself on any Unix-like platform, +configure or re-configure SolveSpace to produce a debug build, and +then build it: + + cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug [other cmake args...] + make + +If you are building SolveSpace yourself using the Visual Studio IDE, +select Debug from the Solution Configurations list box on the toolbar, +and build the solution. + +### Debugging with gdb + +gdb is a debugger that is mostly used on Linux. First, run SolveSpace +under debugging: + + gdb [path to solvespace executable] + (gdb) run + +Then, reproduce the crash. After the crash, attach the output in +the console, as well as output of the following gdb commands to +a bug report: + + (gdb) backtrace + (gdb) info locals + +If the crash is not easy to reproduce, please generate a core file, +which you can use to resume the debugging session later, and provide +any other information that is requested: + + (gdb) generate-core-file + +This will generate a large file called like `core.1234` in the current +directory; it can be later re-loaded using `gdb --core core.1234`. + +### Debugging with lldb + +lldb is a debugger that is mostly used on macOS. First, run SolveSpace +under debugging: + + lldb [path to solvespace executable] + (lldb) run + +Then, reproduce the crash. After the crash, attach the output in +the console, as well as output of the following gdb commands to +a bug report: + + (lldb) backtrace all + (lldb) frame variable + +If the crash is not easy to reproduce, please generate a core file, +which you can use to resume the debugging session later, and provide +any other information that is requested: + + (lldb) process save-core "core" + +This will generate a large file called `core` in the current +directory; it can be later re-loaded using `lldb -c core`. + +### Debugging GUI-related bugs on Linux + +There are several environment variables available that make crashes +earlier and errors more informative. Before running SolveSpace, run +the following commands in your shell: + + export G_DEBUG=fatal_warnings + export LIBGL_DEBUG=1 + export MESA_DEBUG=1 diff --git a/README.md b/README.md index 838b92f..394e33d 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,64 @@ +SolveSpace Logo + SolveSpace ========== +[![Build Status](https://travis-ci.com/solvespace/solvespace.svg?branch=master)](https://travis-ci.com/solvespace/solvespace) +[![solvespace](https://snapcraft.io/solvespace/badge.svg)](https://snapcraft.io/solvespace) +[![solvespace](https://snapcraft.io/solvespace/trending.svg?name=0)](https://snapcraft.io/solvespace) This repository contains the source code of [SolveSpace][], a parametric 2d/3d CAD. [solvespace]: http://solvespace.com +Community +--------- + +The official SolveSpace [website][sswebsite] has [tutorials][sstutorial], +[reference manual][ssref] and a [forum][ssforum]; there is also an official +IRC channel [#solvespace at irc.freenode.net][ssirc]. + +[sswebsite]: http://solvespace.com/ +[ssref]: http://solvespace.com/ref.pl +[sstutorial]: http://solvespace.com/tutorial.pl +[ssforum]: http://solvespace.com/forum.pl +[ssirc]: https://webchat.freenode.net/?channels=solvespace + Installation ------------ -### Mac OS X (>=10.6 64-bit), Debian (>=jessie) and Ubuntu (>=trusty) +### Via official binary packages -Binary packages for Mac OS X and Debian derivatives are available -via [GitHub releases][rel]. +_Official_ release binary packages for macOS (>=10.6 64-bit) and Windows (>=Vista 32-bit) are +available via [GitHub releases][rel]. These packages are automatically built by +the SolveSpace maintainers for each stable release. [rel]: https://github.com/solvespace/solvespace/releases -### Other systems +### Via Snap Store + +Builds from master are automatically released to the `edge` channel in the Snap Store. Those packages contain the latest improvements, but receive less testing than release builds. + +Future official releases will appear in the `stable` channel. + +[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/solvespace) + +Or install from a terminal: + +``` +snap install --edge solvespace +``` + +### Via third-party binary packages + +_Third-party_ nightly binary packages for Debian and Ubuntu are available +via [notesalexp.org][notesalexp]. These packages are automatically built from non-released +source code. The SolveSpace maintainers do not control the contents of these packages +and cannot guarantee their functionality. + +[notesalexp]: https://notesalexp.org/packages/en/source/solvespace/ + +### Via source code See below. @@ -25,109 +67,165 @@ Building on Linux ### Building for Linux -You will need CMake, libpng, zlib, json-c, fontconfig, freetype, gtkmm 2.4, -pangomm 1.4, OpenGL, OpenGL GLU and OpenGL GLEW, and optionally, the Space Navigator -client library. +You will need the usual build tools, CMake, zlib, libpng, cairo, freetype. +To build the GUI, you will need fontconfig, gtkmm 3.0 (version 3.16 or later), pangomm 1.4, +OpenGL and OpenGL GLU, and optionally, the Space Navigator client library. On a Debian derivative (e.g. Ubuntu) these can be installed with: - apt-get install libpng12-dev libjson-c-dev libfreetype6-dev \ - libfontconfig1-dev libgtkmm-2.4-dev libpangomm-1.4-dev \ - libgl-dev libglu-dev libglew-dev libspnav-dev cmake + sudo apt install git build-essential cmake zlib1g-dev libpng-dev \ + libcairo2-dev libfreetype6-dev libjson-c-dev \ + libfontconfig1-dev libgtkmm-3.0-dev libpangomm-1.4-dev \ + libgl-dev libglu-dev libspnav-dev + +On a Redhat derivative (e.g. Fedora) the dependencies can be installed with: -Before building, check out the necessary submodules: + sudo dnf install git gcc-c++ cmake zlib-devel libpng-devel \ + cairo-devel freetype-devel json-c-devel \ + fontconfig-devel gtkmm30-devel pangomm-devel \ + mesa-libGL-devel mesa-libGLU-devel libspnav-devel - git submodule update --init extlib/libdxfrw +Before building, check out the project and the necessary submodules: + + git clone https://github.com/solvespace/solvespace + cd solvespace + git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc After that, build SolveSpace as following: mkdir build cd build - cmake .. + cmake .. -DCMAKE_BUILD_TYPE=Release make sudo make install -A fully functional port to GTK3 is available, but not recommended -for use due to bugs in this toolkit. +The graphical interface is built as `build/bin/solvespace`, and the command-line interface +is built as `build/bin/solvespace-cli`. It is possible to build only the command-line interface +by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation. ### Building for Windows -You will need CMake, a Windows cross-compiler, and Wine with binfmt support. -On a Debian derivative (e.g. Ubuntu) these can be installed with: +Ubuntu will require 20.04 or above. Cross-compiling with WSL is also confirmed to work. - apt-get install cmake mingw-w64 wine-binfmt +You will need the usual build tools, CMake, a Windows cross-compiler, and flatc. On a Debian derivative (e.g. Ubuntu) these can be installed with: -Before building, check out the necessary submodules: + apt-get install git build-essential cmake mingw-w64 libflatbuffers-dev +Before building, check out the project and the necessary submodules: + + git clone https://github.com/solvespace/solvespace + cd solvespace git submodule update --init -After that, build 32-bit SolveSpace as following: +Build 64-bit SolveSpace with the following: mkdir build cd build - cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw32.cmake .. - make solvespace + cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw64.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DFLATC=$(which flatc) + make + +The graphical interface is built as `build/bin/solvespace.exe`, and the command-line interface +is built as `build/bin/solvespace-cli.exe`. + +Space Navigator support will not be available. -Or, build 64-bit SolveSpace as following: +If using Ubuntu to cross-compile, Ubuntu 17.10 or newer (or, alternatively, MinGW from the Ubuntu +17.10 repositories) is required. + +Building on macOS +----------------- + +You will need git, XCode tools and CMake. Git and CMake can be installed +via [Homebrew][]: + + brew install git cmake + +XCode has to be installed via AppStore or [the Apple website][appledeveloper]; +it requires a free Apple ID. + +Before building, check out the project and the necessary submodules: + + git clone https://github.com/solvespace/solvespace + cd solvespace + git submodule update --init + +After that, build SolveSpace as following: mkdir build cd build - cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-mingw64.cmake .. - make solvespace + cmake .. -DCMAKE_BUILD_TYPE=Release + make -The application is built as `build/src/solvespace.exe`. +Alternatively, generate an XCode project, open it, and build the "Release" scheme: -Space Navigator support will not be available. + mkdir build + cd build + cmake .. -G Xcode + +The application is built in `build/bin/SolveSpace.app`, the graphical interface executable +is `build/bin/SolveSpace.app/Contents/MacOS/SolveSpace`, and the command-line interface executable +is `build/bin/SolveSpace.app/Contents/MacOS/solvespace-cli`. -Building on Mac OS X --------------------- +[homebrew]: https://brew.sh/ +[appledeveloper]: https://developer.apple.com/download/ -You will need XCode tools, CMake, libpng and Freetype. Assuming you use -[homebrew][], these can be installed with: +Building on OpenBSD +------------------- - brew install cmake libpng freetype +You will need git, cmake, libexecinfo, libpng, gtk3mm and pangomm. +These can be installed from the ports tree: -XCode has to be installed via AppStore; it requires a free Apple ID. + pkg_add -U git cmake libexecinfo png json-c gtk3mm pangomm -Before building, check out the necessary submodules: +Before building, check out the project and the necessary submodules: - git submodule update --init extlib/libdxfrw + git clone https://github.com/solvespace/solvespace + cd solvespace + git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc After that, build SolveSpace as following: mkdir build cd build - cmake .. + cmake .. -DCMAKE_BUILD_TYPE=Release make -The app bundle is built in `build/src/solvespace.app`. - -[homebrew]: http://brew.sh/ +Unfortunately, on OpenBSD, the produced executables are not filesystem location independent +and must be installed before use. By default, the graphical interface is installed to +`/usr/local/bin/solvespace`, and the command-line interface is built as +`/usr/local/bin/solvespace-cli`. It is possible to build only the command-line interface +by passing the `-DENABLE_GUI=OFF` flag to the cmake invocation. Building on Windows ------------------- -You will need [cmake][cmakewin] and Visual C++. +You will need [git][gitwin], [cmake][cmakewin] and a C++ compiler +(either Visual C++ or MinGW). If using Visual C++, Visual Studio 2015 +or later is required. -### GUI build +### Building with Visual Studio IDE Check out the git submodules. Create a directory `build` in the source tree and point cmake-gui to the source tree and that directory. Press "Configure" and "Generate", then open `build\solvespace.sln` with Visual C++ and build it. -### Command-line build +### Building with Visual Studio in a command prompt First, ensure that git and cl (the Visual C++ compiler driver) are in your `%PATH%`; the latter is usually done by invoking `vcvarsall.bat` from your Visual Studio install. Then, run the following in cmd or PowerShell: + git clone https://github.com/solvespace/solvespace + cd solvespace git submodule update --init mkdir build cd build - cmake .. -G "NMake Makefiles" + cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release nmake -### MSVC build +### Building with MinGW It is also possible to build SolveSpace using [MinGW][mingw], though Space Navigator support will be disabled. @@ -135,16 +233,27 @@ Space Navigator support will be disabled. First, ensure that git and gcc are in your `$PATH`. Then, run the following in bash: + git clone https://github.com/solvespace/solvespace + cd solvespace git submodule update --init mkdir build cd build - cmake .. + cmake .. -DCMAKE_BUILD_TYPE=Release make +[gitwin]: https://git-scm.com/download/win [cmakewin]: http://www.cmake.org/download/#latest [mingw]: http://www.mingw.org/ +Contributing +------------ + +See the [guide for contributors](CONTRIBUTING.md) for the best way to file issues, contribute code, +and debug SolveSpace. + License ------- -SolveSpace is distributed under the terms of the [GPL3 license](COPYING.txt). +SolveSpace is distributed under the terms of the [GPL v3 license](COPYING.txt). It is possible +to license SolveSpace for use in a commercial application; to do so, +[contact](http://solvespace.com/contact.pl) the developers. diff --git a/THIRD_PARTIES.txt b/THIRD_PARTIES.txt new file mode 100644 index 0000000..ad04bf6 --- /dev/null +++ b/THIRD_PARTIES.txt @@ -0,0 +1,50 @@ +This file outlines third party licenses that apply to the files and resources +listed with them. All file paths are relative to the SolveSpace project root. + +When redistributing SolveSpace, make sure to also reproduce this file such that +the paths indicating the scope of each license remain intact. +================================================================================ + + Bitstream Vera, ./res/fonts/BitstreamVeraSans-Roman-builtin.ttf + ~ +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a +trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +the fonts accompanying this license (“Fonts”) and associated documentation files +(the “Font Software”), to reproduce and distribute the Font Software, including +without limitation the rights to use, copy, merge, publish, distribute, and/or +sell copies of the Font Software, and to permit persons to whom the Font +Software is furnished to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice shall be +included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular the +designs of glyphs or characters in the Fonts may be modified and additional +glyphs or characters may be added to the Fonts, only if the fonts are renamed to +names not containing either the words “Bitstream” or the word “Vera”. + +This License becomes null and void to the extent applicable to Fonts or Font +Software that has been modified and is distributed under the “Bitstream Vera” +names. + +The Font Software may be sold as part of a larger software package but no copy +of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR +OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, +INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR +FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the names of GNOME, the GNOME Foundation, +and Bitstream Inc., shall not be used in advertising or otherwise to promote the +sale, use or other dealings in this Font Software without prior written +authorization from the GNOME Foundation or Bitstream Inc., respectively. For +further information, contact: fonts at gnome dot org. + + ----------- diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 4f4b3ae..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: 2.2.{build} -clone_depth: 1 -before_build: - - git submodule update --init - - mkdir build - - cd build - - set tag=x%APPVEYOR_REPO_TAG_NAME% - - if %tag:~,2% == xv (set BUILD_TYPE=RelWithDebInfo) else (set BUILD_TYPE=Debug) - - cmake -G"Visual Studio 12" -T v120_xp -DCMAKE_BUILD_TYPE=%BUILD_TYPE% .. -build_script: - - msbuild "src\solvespace.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" -artifacts: - - path: build\src\Debug\solvespace.exe - name: solvespace.exe - - path: build\src\Debug\solvespace.pdb - name: solvespace.pdb - - path: build\src\RelWithDebInfo\solvespace.exe - name: solvespace.exe - - path: build\src\RelWithDebInfo\solvespace.pdb - name: solvespace.pdb -deploy: - - provider: GitHub - auth_token: - secure: P9/pf2nM+jlWKe7pCjMp41HycBNP/+5AsmE/TETrDUoBOa/9WFHelqdVFrbRn9IC - description: "" - artifact: solvespace.exe - on: - appveyor_repo_tag: true diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt new file mode 100644 index 0000000..26172f1 --- /dev/null +++ b/bench/CMakeLists.txt @@ -0,0 +1,17 @@ +# benchmark runner + +foreach(pkg_config_lib CAIRO) + include_directories(${${pkg_config_lib}_INCLUDE_DIRS}) + link_directories(${${pkg_config_lib}_LIBRARY_DIRS}) +endforeach() + +add_executable(solvespace-benchmark + harness.cpp + $) + +target_link_libraries(solvespace-benchmark + solvespace-core + solvespace-headless) + +add_dependencies(solvespace-benchmark + resources) diff --git a/bench/harness.cpp b/bench/harness.cpp new file mode 100644 index 0000000..4180558 --- /dev/null +++ b/bench/harness.cpp @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// Our harness for running benchmarks. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" + +static bool RunBenchmark(std::function setupFn, + std::function benchFn, + std::function teardownFn, + size_t minIter = 5, double minTime = 5.0) { + // Warmup + setupFn(); + if(!benchFn()) { + fprintf(stderr, "Benchmark failed\n"); + return false; + } + teardownFn(); + + // Benchmark + size_t iter = 0; + double time = 0.0; + while(iter < minIter || time < minTime) { + setupFn(); + auto testStartTime = std::chrono::steady_clock::now(); + benchFn(); + auto testEndTime = std::chrono::steady_clock::now(); + teardownFn(); + + std::chrono::duration testTime = testEndTime - testStartTime; + time += testTime.count(); + iter += 1; + } + + // Report + fprintf(stdout, "Iterations: %zd\n", iter); + fprintf(stdout, "Time: %.3f s\n", time); + fprintf(stdout, "Per iter.: %.3f s\n", time / (double)iter); + + return true; +} + +int main(int argc, char **argv) { + std::vector args = Platform::InitCli(argc, argv); + + std::string mode; + Platform::Path filename; + if(args.size() == 3) { + mode = args[1]; + filename = Platform::Path::From(args[2]); + } else { + fprintf(stderr, "Usage: %s [mode] [filename]\n", args[0].c_str()); + fprintf(stderr, "Mode can be one of: load.\n"); + return 1; + } + + bool result = false; + if(mode == "load") { + result = RunBenchmark( + [] { + SS.Init(); + }, + [&] { + if(!SS.LoadFromFile(filename)) + return false; + SS.AfterNewFile(); + return true; + }, + [] { + SK.Clear(); + SS.Clear(); + }); + } else { + fprintf(stderr, "Unknown mode \"%s\"\n", mode.c_str()); + } + + return (result == true ? 0 : 1); +} diff --git a/cmake/AddVendoredSubdirectory.cmake b/cmake/AddVendoredSubdirectory.cmake new file mode 100644 index 0000000..9d2edf6 --- /dev/null +++ b/cmake/AddVendoredSubdirectory.cmake @@ -0,0 +1,10 @@ +# Equivalent to add_subdirectory(... EXCLUDE_FROM_ALL), but also disables +# all warnings. + +include(DisableWarnings) + +function(add_vendored_subdirectory PATH) + disable_warnings() + + add_subdirectory(${PATH} EXCLUDE_FROM_ALL) +endfunction() diff --git a/cmake/DisableWarnings.cmake b/cmake/DisableWarnings.cmake new file mode 100644 index 0000000..b79ef9c --- /dev/null +++ b/cmake/DisableWarnings.cmake @@ -0,0 +1,15 @@ +# Disables all warnings on MSVC and GNU-compatible compilers. + +function(disable_warnings) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -w" PARENT_SCOPE) + elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W0" PARENT_SCOPE) + endif() + + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w" PARENT_SCOPE) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W0" PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/FindSpaceWare.cmake b/cmake/FindSpaceWare.cmake index 135384a..fb6073c 100644 --- a/cmake/FindSpaceWare.cmake +++ b/cmake/FindSpaceWare.cmake @@ -4,7 +4,7 @@ # # SPACEWARE_INCLUDE_DIR - header location # SPACEWARE_LIBRARIES - library to link against -# SPACEWARE_FOUND - true if pugixml was found. +# SPACEWARE_FOUND - true if libspnav was found. if(UNIX) diff --git a/cmake/FindVendoredPackage.cmake b/cmake/FindVendoredPackage.cmake new file mode 100644 index 0000000..6227f6c --- /dev/null +++ b/cmake/FindVendoredPackage.cmake @@ -0,0 +1,56 @@ +# Find the given library in the system locations, or build in-tree if not found. +# +# Arguments: +# PKG_NAME - name of the package as passed to find_package +# PKG_PATH - name of the source tree relative to extlib/ +# +# The rest of the arguments are VARIABLE VALUE pairs. If the library is not found, +# every VARIABLE will be set to VALUE and find_package will be rerun with the REQUIRED flag. +# Regardless of where the library was found, only the specified VARIABLEs that start with +# ${PKG_NAME} will be set in the parent scope. +# +# All warnings in the in-tree package are disabled. + +include(DisableWarnings) + +function(find_vendored_package PKG_NAME PKG_PATH) + if(NOT FORCE_VENDORED_${PKG_NAME}) + find_package(${PKG_NAME}) + endif() + + set(cfg_name) + foreach(item ${ARGN}) + if(NOT cfg_name) + set(cfg_name ${item}) + else() + set(${cfg_name} ${item} CACHE INTERNAL "") + set(cfg_name) + endif() + endforeach() + + disable_warnings() + + string(TOUPPER ${PKG_NAME} VAR_NAME) + if(NOT ${VAR_NAME}_FOUND) + message(STATUS "Using in-tree ${PKG_PATH}") + set(${VAR_NAME}_IN_TREE YES CACHE INTERNAL "") + + add_subdirectory(extlib/${PKG_PATH} EXCLUDE_FROM_ALL) + find_package(${PKG_NAME} REQUIRED) + elseif(${VAR_NAME}_IN_TREE) + add_subdirectory(extlib/${PKG_PATH} EXCLUDE_FROM_ALL) + endif() + + # Now put everything we just discovered into the cache. + set(cfg_name) + foreach(item ${ARGN} ${VAR_NAME}_FOUND) + if(NOT cfg_name) + set(cfg_name ${item}) + else() + if(cfg_name MATCHES "^${VAR_NAME}") + set(${cfg_name} "${${cfg_name}}" CACHE INTERNAL "") + endif() + set(cfg_name) + endif() + endforeach() +endfunction() diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in index c215c44..668d1f7 100644 --- a/cmake/MacOSXBundleInfo.plist.in +++ b/cmake/MacOSXBundleInfo.plist.in @@ -2,10 +2,12 @@ + CFBundleIdentifier + com.solvespace CFBundleDevelopmentRegion English CFBundleExecutable - solvespace + SolveSpace CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -13,11 +15,11 @@ CFBundlePackageType APPL CFBundleVersion - ${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR} + ${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH} CFBundleShortVersionString ${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR} NSHumanReadableCopyright - © 2008-2015 Jonathan Westhues and other authors + © 2008-2016 Jonathan Westhues and other authors NSPrincipalClass NSApplication NSMainNibFile diff --git a/cmake/Toolchain-mingw32.cmake b/cmake/Toolchain-mingw32.cmake index a3591c8..a8767fd 100644 --- a/cmake/Toolchain-mingw32.cmake +++ b/cmake/Toolchain-mingw32.cmake @@ -1,13 +1,15 @@ -SET(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_NAME Windows) -SET(TRIPLE i686-w64-mingw32) +set(TRIPLE i686-w64-mingw32) -SET(CMAKE_C_COMPILER ${TRIPLE}-gcc) -SET(CMAKE_CXX_COMPILER ${TRIPLE}-g++) -SET(CMAKE_RC_COMPILER ${TRIPLE}-windres) +set(CMAKE_C_COMPILER ${TRIPLE}-gcc) +set(CMAKE_CXX_COMPILER ${TRIPLE}-g++) +set(CMAKE_RC_COMPILER ${TRIPLE}-windres) -SET(CMAKE_FIND_ROOT_PATH /usr/${TRIPLE}) +set(CMAKE_FIND_ROOT_PATH /usr/${TRIPLE}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +set(ENV{PKG_CONFIG_LIBDIR} /usr/${TRIPLE}/lib/pkgconfig) diff --git a/cmake/Toolchain-mingw64.cmake b/cmake/Toolchain-mingw64.cmake index 6841214..7f85f35 100644 --- a/cmake/Toolchain-mingw64.cmake +++ b/cmake/Toolchain-mingw64.cmake @@ -1,13 +1,15 @@ -SET(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_NAME Windows) -SET(TRIPLE x86_64-w64-mingw32) +set(TRIPLE x86_64-w64-mingw32) -SET(CMAKE_C_COMPILER ${TRIPLE}-gcc) -SET(CMAKE_CXX_COMPILER ${TRIPLE}-g++) -SET(CMAKE_RC_COMPILER ${TRIPLE}-windres) +set(CMAKE_C_COMPILER ${TRIPLE}-gcc) +set(CMAKE_CXX_COMPILER ${TRIPLE}-g++) +set(CMAKE_RC_COMPILER ${TRIPLE}-windres) -SET(CMAKE_FIND_ROOT_PATH /usr/${TRIPLE}) +set(CMAKE_FIND_ROOT_PATH /usr/${TRIPLE}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +set(ENV{PKG_CONFIG_LIBDIR} /usr/${TRIPLE}/lib/pkgconfig) diff --git a/developer_docs/Solver_Transforms.txt b/developer_docs/Solver_Transforms.txt new file mode 100644 index 0000000..1ea3ae5 --- /dev/null +++ b/developer_docs/Solver_Transforms.txt @@ -0,0 +1,154 @@ + +CREATING NEW GROUPS in 3D +================================= + +New Transformed Entity Types: +----------------------------- +New construction tools may require new transformations of entities - Points, +Normals, and Faces. A number of "Types" exist to transform an entity to a new +location. The term "Type" refers to the type of transformation an entity was +created from, and should not be confused with the 3 kinds of Entity (there are +other entities but they rely on others of the 3 base types for position and +orientation information, so they don't directly transform). A list of +Types is at the top of the EntityBase class definition: + + enum class Type : uint32_t { + POINT_IN_3D = 2000, + POINT_IN_2D = 2001, + POINT_N_TRANS = 2010, + POINT_N_ROT_TRANS = 2011, + POINT_N_COPY = 2012, + POINT_N_ROT_AA = 2013, + POINT_N_ROT_AXIS_TRANS = 2014, + + NORMAL_IN_3D = 3000, + NORMAL_IN_2D = 3001, + NORMAL_N_COPY = 3010, + NORMAL_N_ROT = 3011, + NORMAL_N_ROT_AA = 3012, + + FACE_NORMAL_PT = 5000, + FACE_XPROD = 5001, + FACE_N_ROT_TRANS = 5002, + FACE_N_TRANS = 5003, + FACE_N_ROT_AA = 5004, + +Some of the point definitions with _N_ in the name are points defined by +N application of a transformation. The number of times a particular entity +is transformed is given in the member variable "timesApplied". The following +is a dectription of the various transformation Types. + +POINT_N_TRANS: Translates a point by a vector defined by param[0],param[1],param[2] + the vector is multiplied by timesApplied. + +POINT_N_ROT_TRANS: Rotates a point via quaternion param[3],param[4],param[5],param[6] + and then translates it by vector param[0],param[1],param[2] + +POINT_N_COPY: A non-transformed copy of a point - numeric copy? + +POINT_N_ROT_AA: A point rotated arount point param[0],param[1],param[2] Where the + angle is given by param[3]*timesApplied (times 2?) and the axis + of rotation defined by param[4],param[5],param[6] + +POINT_N_ROT_AXIS_TRANS: Same as POINT_N_ROT_AA but after rotation, the point is + translated along the rotation axis by distance param[7]. + +NORMAL_N_COPY A non-transformed copy of a normal - numeric copy? + +NORMAL_N_ROT: A normal rotated by a quaternion defeined by param[0],param[1],param[2],param[3] + +NORMAL_N_ROT_AA A normal rotated by timesApplied*param[0] around the axis + specified by param[1],param[2],parma[3] + +FACE_N_ROT_TRANS A face rotated then translated. Rotation is defined by + quaternion param[3],param[4],param[5],param[6]. The translation + is defined by param[0],param[1],param[2]. + +FACE_N_TRANS: Translates a face by a vector defined by param[0],param[1],param[2] + faces are defined by a point and a normal, so this just moves the point. + +FACE_N_ROT_AA Face rotated about point param[0],param[1],param[2]. The axis + is param[4],param[5],param[6]. Angle is timesApplied*param[3]. + + +All entities are copied by the function Group::CopyEntity() which has a CopyAs +parameter to indicate which kind of copy is to be made. The mapping from CopyAs to +Entity Types can be found in that function. Most point types get copied the same +way depending on CopyAs. Several of the normals get copied to the same new +entity Type because they are unaffected by translation - you don't care if CopyAs +specified a translation with rotation or just a rotation, a normal is affected the +same in either case. The mapping from entity type to new entity type has to be +decoded from the cases and if-else logic in that function. + +It is important that a transformation be properly applied to all three of the +fundamental entities - points, normals, and surfaces. + +FUNCTIONS THAT MAY NEED TO BE EXTENDED when new entity types are defined: + +These functions have default cases, so they only need to be extended to return True: +EntityBase::IsPoint() +EntityBase::IsNormal() +EntityBAse::IsFace() + +For new normal transforms the following should be filled in: +Quaternion EntityBase::NormalGetNum() +void EntityBase::NormalForceTo(Quaternion q) +ExprQuaternion EntityBase::NormalGetExprs() + +For points: +Vector EntityBase::PointGetNum() +void EntityBase::PointForceTo(Vector p) +ExprVector EntityBase::PointGetExprs() + +For Faces: +ExprVector EntityBase::FaceGetNormalExprs() +Vector EntityBase::FaceGetNormalNum() +ExprVector EntityBase::FaceGetPointExprs() +Vector EntityBase::FaceGetPointNum() + +The basic model for these transformed entities is that the group containing them +will have parameters that define the transformed entity in terms of the one it +was copied from. For example, in PointGetNum() under the case POINT_N_TRANS we see: + + Vector trans = Vector::From(param[0], param[1], param[2]); + p = numPoint.Plus(trans.ScaledBy(timesApplied)); + break; + +This take the original "numPoint" and adds a vector created from 3 parameters and +multiplied by "timesApplied". This is used in the step-translating groups. It's also +used in extrude groups where there are constraints on the 3 parameters to keep the +vector perpendicular to the sketch it was extruded from. This function returns a +numerical version of the copied point, hence "GetNum" in the name. + +Another function PointGetExprs() returns the same coordinates of the point but +returns an expression vector. When the user applies a constraint to a point, this +function is called to get algebraic expressions for the point that are suitable for +use in the solver. Most points will not be constrained directly, so expressions +are not needed for them. It is also notable that the expressions are not part of +the entity itself. + +The ForceTo() functions are shortcuts for using the solver. They are passed the +desired location of a point (or orientation of a normal...) and have the opportunity +to back-calculate what the group parameters should be to place it there. This is +used for mouse dragging of copied entites. It is notable that the constraints will +still be applied afterward, but this is a good shortcut. + +When creating a new entity transformation, the first thing to do is define the +numerical copy (xxxGetNum). That should allow display. Dragging will not work +until ForceTo is implemented, and user constraints will not work until GetExprs() +is completed. Most of these functions have an assert that will fire if the required +new case is missing. This is may not be a complete list of everything you need to +make new entity transformations. + +One thing of note, parameters in both entities and groups are stored by their handle. +An entity can have up to 8 parameters to define how it is transformed from another +entity. By convention this array of parameter handles matches that of the group but +this is probably not strictly necessary. + +ADDING GROUP CONSTRAINTS: +The example above where a point is copied via POINT_N_TRANS for both EXTRUDE and +STEP TRANSLATING groups is a good example here. For the EXTRUDE case, two constraint +equations are added to keep the offset vector perpendicular to the sketch. These +equations are created in Group::GenerateEquations() and are nothing more than an +expression which is implicitly set equal to zero in the solver. + diff --git a/exposed/CDemo.c b/exposed/CDemo.c index 574f100..cf6107e 100644 --- a/exposed/CDemo.c +++ b/exposed/CDemo.c @@ -5,18 +5,13 @@ * * Copyright 2008-2013 Jonathan Westhues. *---------------------------------------------------------------------------*/ -#ifdef HAVE_CONFIG_H -# include -#endif #ifdef WIN32 # include #endif #include #include #include -#ifdef HAVE_STDINT_H -# include -#endif +#include #include @@ -36,7 +31,7 @@ static void *CheckMalloc(size_t n) * An example of a constraint in 3d. We create a single group, with some * entities and constraints. *---------------------------------------------------------------------------*/ -void Example3d(void) +void Example3d() { /* This will contain a single group, which will arbitrarily number 1. */ Slvs_hGroup g = 1; @@ -88,7 +83,7 @@ void Example3d(void) * along the reference frame's xy plane. In a second group, we create some * entities in that group and dimension them. *---------------------------------------------------------------------------*/ -void Example2d(void) +void Example2d() { Slvs_hGroup g; double qw, qx, qy, qz; @@ -254,7 +249,7 @@ void Example2d(void) } } -int main(void) +int main() { sys.param = CheckMalloc(50*sizeof(sys.param[0])); sys.entity = CheckMalloc(50*sizeof(sys.entity[0])); diff --git a/exposed/DOC.txt b/exposed/DOC.txt index 743349b..f64f3ac 100644 --- a/exposed/DOC.txt +++ b/exposed/DOC.txt @@ -253,9 +253,9 @@ TYPES OF CONSTRAINTS Many constraints can apply either in 3d, or in a workplane. This is determined by the wrkpl member of the constraint. If that member is set to SLVS_FREE_IN_3D, then the constraint applies in 3d. If that member -is set equal to a workplane, the the constraint applies projected into -that workplane. (For example, a constraint on the distance between two -points actually applies to the projected distance). +is set equal to a workplane, the constraint applies projected into that +workplane. (For example, a constraint on the distance between two points +actually applies to the projected distance). Constraints that may be used in 3d or projected into a workplane are marked with a single star (*). Constraints that must always be used with @@ -456,7 +456,7 @@ USING THE SOLVER ================ The solver is provided as a DLL, and will be usable with most -Windows-based developement tools. Examples are provided: +Windows-based development tools. Examples are provided: in C/C++ - CDemo.c diff --git a/exposed/VbDemo.vb b/exposed/VbDemo.vb index 6781fc4..e702957 100644 --- a/exposed/VbDemo.vb +++ b/exposed/VbDemo.vb @@ -606,7 +606,7 @@ Module VbDemo End Function ' After a failing call to Solve(), this returns the list of - ' constraints, identified by ther handle, that would fix the + ' constraints, identified by their handle, that would fix the ' system if they were deleted. This list will be populated only ' if calculateFaileds is True in the Solve() call. Public Function GetFaileds() As List(Of UInteger) diff --git a/extlib/libdxfrw/drw_base.h b/extlib/libdxfrw/drw_base.h index 648f3e6..87fc11f 100644 --- a/extlib/libdxfrw/drw_base.h +++ b/extlib/libdxfrw/drw_base.h @@ -101,8 +101,25 @@ enum DBG_LEVEL { //! Special codes for colors enum ColorCodes { + ColorByBlock = 0, + Red = 1, + Yellow = 2, + Green = 3, + Cyan = 4, + Blue = 5, + Magenta = 6, + White = 7, + Gray = 8, + Brown = 15, + LRed = 23, + LGreen = 121, + LCyan = 131, + LBlue = 163, + LMagenta = 221, + Black = 250, + LGray = 252, ColorByLayer = 256, - ColorByBlock = 0 + }; //! Spaces @@ -149,15 +166,11 @@ enum TransparencyCodes { */ class DRW_Coord { public: - DRW_Coord() { x = 0; y = 0; z = 0; } + DRW_Coord() = default; DRW_Coord(double ix, double iy, double iz) { x = ix; y = iy; z = iz; } - DRW_Coord operator = (const DRW_Coord& data) { - x = data.x; y = data.y; z = data.z; - return *this; - } /*!< convert to unitary vector */ void unitize(){ double dist; @@ -170,9 +183,9 @@ public: } public: - double x; - double y; - double z; + double x = 0; + double y = 0; + double z = 0; }; diff --git a/extlib/libdxfrw/drw_entities.cpp b/extlib/libdxfrw/drw_entities.cpp index 0265f56..33b9735 100644 --- a/extlib/libdxfrw/drw_entities.cpp +++ b/extlib/libdxfrw/drw_entities.cpp @@ -17,9 +17,9 @@ #include "intern/drw_dbg.h" -//! Calculate arbitary axis +//! Calculate arbitrary axis /*! -* Calculate arbitary axis for apply extrusions +* Calculate arbitrary axis for apply extrusions * @author Rallaz */ void DRW_Entity::calculateAxis(DRW_Coord extPoint){ @@ -50,9 +50,9 @@ void DRW_Entity::calculateAxis(DRW_Coord extPoint){ extAxisY.unitize(); } -//! Extrude a point using arbitary axis +//! Extrude a point using arbitrary axis /*! -* apply extrusion in a point using arbitary axis (previous calculated) +* apply extrusion in a point using arbitrary axis (previous calculated) * @author Rallaz */ void DRW_Entity::extrudePoint(DRW_Coord extPoint, DRW_Coord *point){ @@ -1335,10 +1335,10 @@ bool DRW_Text::parseDwg(DRW::Version version, dwgBuffer *buf, duint32 bs){ return ret; DRW_DBG("\n***************************** parsing text *********************************************\n"); - // DataFlags RC Used to determine presence of subsquent data, set to 0xFF for R14- + // DataFlags RC Used to determine presence of subsequent data, set to 0xFF for R14- duint8 data_flags = 0x00; if (version > DRW::AC1014) {//2000+ - data_flags = buf->getRawChar8(); /* DataFlags RC Used to determine presence of subsquent data */ + data_flags = buf->getRawChar8(); /* DataFlags RC Used to determine presence of subsequent data */ DRW_DBG("data_flags: "); DRW_DBG(data_flags); DRW_DBG("\n"); if ( !(data_flags & 0x01) ) { /* Elevation RD --- present if !(DataFlags & 0x01) */ basePoint.z = buf->getRawDouble(); @@ -1520,7 +1520,7 @@ bool DRW_MText::parseDwg(DRW::Version version, dwgBuffer *buf, duint32 bs){ DRW_UNUSED(ext_ht); /* Extents wid BD Undocumented and not present in DXF or entget The extents rectangle, when rotated the same as the text, fits the actual text image on - the screen (altough we've seen it include an extra row of text in height). */ + the screen (although we've seen it include an extra row of text in height). */ double ext_wid = buf->getBitDouble(); DRW_UNUSED(ext_wid); /* Text TV 1 All text in one long string (without '\n's 3 for line wrapping). @@ -2018,8 +2018,8 @@ bool DRW_Hatch::parseDwg(DRW::Version version, dwgBuffer *buf, duint32 bs){ DRW_DBG("\ndef line: "); DRW_DBG(angleL); DRW_DBG(","); DRW_DBG(ptL.x); DRW_DBG(","); DRW_DBG(ptL.y); DRW_DBG(","); DRW_DBG(offL.x); DRW_DBG(","); DRW_DBG(offL.y); DRW_DBG(","); DRW_DBG(angleL); for (duint16 i = 0 ; i < numDashL; ++i){ - double lenghtL = buf->getBitDouble(); - DRW_DBG(","); DRW_DBG(lenghtL); + double lengthL = buf->getBitDouble(); + DRW_DBG(","); DRW_DBG(lengthL); } }//end deflines } //end not solid @@ -2433,6 +2433,15 @@ void DRW_Dimension::parseCode(int code, dxfReader *reader){ case 51: hdir = reader->getDouble(); break; + case 210: + extPoint.x = reader->getDouble(); + break; + case 220: + extPoint.y = reader->getDouble(); + break; + case 230: + extPoint.z = reader->getDouble(); + break; default: DRW_Entity::parseCode(code, reader); break; @@ -2998,7 +3007,7 @@ bool DRW_Viewport::parseDwg(DRW::Version version, dwgBuffer *buf, duint32 bs){ frozenLyCount = buf->getBitLong(); DRW_DBG("Frozen Layer count?: "); DRW_DBG(frozenLyCount); DRW_DBG("\n"); DRW_DBG("Status Flags?: "); DRW_DBG(buf->getBitLong()); DRW_DBG("\n"); - //RLZ: Warning needed separate string bufer + //RLZ: Warning needed separate string buffer DRW_DBG("Style sheet?: "); DRW_DBG(sBuf->getVariableText(version, false)); DRW_DBG("\n"); DRW_DBG("Render mode?: "); DRW_DBG(buf->getRawChar8()); DRW_DBG("\n"); DRW_DBG("UCS OMore...: "); DRW_DBG(buf->getBit()); DRW_DBG("\n"); @@ -3013,8 +3022,8 @@ bool DRW_Viewport::parseDwg(DRW::Version version, dwgBuffer *buf, duint32 bs){ DRW_DBG("ShadePlot Mode...: "); DRW_DBG(buf->getBitShort()); DRW_DBG("\n"); } if (version > DRW::AC1018) {//2007+ - DRW_DBG("Use def Ligth...: "); DRW_DBG(buf->getBit()); DRW_DBG("\n"); - DRW_DBG("Def ligth tipe?: "); DRW_DBG(buf->getRawChar8()); DRW_DBG("\n"); + DRW_DBG("Use def Light...: "); DRW_DBG(buf->getBit()); DRW_DBG("\n"); + DRW_DBG("Def light type?: "); DRW_DBG(buf->getRawChar8()); DRW_DBG("\n"); DRW_DBG("Brightness: "); DRW_DBG(buf->getBitDouble()); DRW_DBG("\n"); DRW_DBG("Contrast: "); DRW_DBG(buf->getBitDouble()); DRW_DBG("\n"); // DRW_DBG("Ambient Cmc or Enc: "); DRW_DBG(buf->getCmColor(version)); DRW_DBG("\n"); diff --git a/extlib/libdxfrw/drw_entities.h b/extlib/libdxfrw/drw_entities.h index be0ab82..ba98527 100644 --- a/extlib/libdxfrw/drw_entities.h +++ b/extlib/libdxfrw/drw_entities.h @@ -965,7 +965,7 @@ public: } void update() { - numedges = objlist.size(); + numedges = (int)objlist.size(); } public: @@ -1190,9 +1190,9 @@ public: double getDir() const { return rot;} /*!< rotation angle of the dimension text, code 53 (optional) default 0 */ void setDir(const double d) { rot = d;} - DRW_Coord getExtrusion(){return extPoint;} /*!< extrusion, code 210, 220 & 230 */ + DRW_Coord getExtrusion() const {return extPoint;} /*!< extrusion, code 210, 220 & 230 */ void setExtrusion(const DRW_Coord p) {extPoint =p;} - std::string getName(){return name;} /*!< Name of the block that contains the entities, code 2 */ + std::string getName() const {return name;} /*!< Name of the block that contains the entities, code 2 */ void setName(const std::string s) {name = s;} // int getType(){ return type;} /*!< Dimension type, code 70 */ bool hasActualMeasurement() const { return hasActual; } @@ -1345,7 +1345,7 @@ public: DRW_Coord getDiameter1Point() const {return getPt5();} /*!< First definition point for diameter, code 15, 25 & 35 */ void setDiameter1Point(const DRW_Coord p){setPt5(p);} - DRW_Coord getDiameter2Point() const {return getDefPoint();} /*!< Oposite point for diameter, code 10, 20 & 30 */ + DRW_Coord getDiameter2Point() const {return getDefPoint();} /*!< Opposite point for diameter, code 10, 20 & 30 */ void setDiameter2Point(const DRW_Coord p){setDefPoint(p);} double getLeaderLength() const {return getRa40();} /*!< Leader length, code 40 */ void setLeaderLength(const double d) {setRa40(d);} diff --git a/extlib/libdxfrw/drw_header.cpp b/extlib/libdxfrw/drw_header.cpp index 47e0643..882752d 100644 --- a/extlib/libdxfrw/drw_header.cpp +++ b/extlib/libdxfrw/drw_header.cpp @@ -491,11 +491,11 @@ void DRW_Header::write(dxfWriter *writer, DRW::Version ver){ writer->writeInt16(70, varInt); else writer->writeInt16(70, 0); - writer->writeString(9, "$DIMSAH"); - if (getInt("$DIMSAH", &varInt)) - writer->writeInt16(70, varInt); - else - writer->writeInt16(70, 0); + writer->writeString(9, "$DIMSAH"); + if (getInt("$DIMSAH", &varInt)) + writer->writeInt16(70, varInt); + else + writer->writeInt16(70, 0); writer->writeString(9, "$DIMBLK1"); if (getStr("$DIMBLK1", &varStr)) if (ver == DRW::AC1009) @@ -944,6 +944,10 @@ void DRW_Header::write(dxfWriter *writer, DRW::Version ver){ writer->writeInt16(70, varInt); } else writer->writeInt16(70, 6); + if (getStr("$TDCREATE", &varStr)) { + writer->writeString(9, "$TDCREATE"); + writer->writeString(40, varStr); + } if (ver > DRW::AC1009) { writer->writeString(9, "$UCSBASE"); if (getStr("$UCSBASE", &varStr)) @@ -1544,7 +1548,7 @@ void DRW_Header::write(dxfWriter *writer, DRW::Version ver){ else writer->writeDouble(40, 50.0); writer->writeString(9, "$CAMERAHEIGHT"); - if (getDouble("$CAMERAHEIGTH", &varDouble)) + if (getDouble("$CAMERAHEIGHT", &varDouble)) writer->writeDouble(40, varDouble); else writer->writeDouble(40, 0.0); @@ -1786,8 +1790,8 @@ bool DRW_Header::parseDwg(DRW::Version version, dwgBuffer *buf, dwgBuffer *hBbuf DRW_DBG("\nbyte size of data: "); DRW_DBG(size); if (version > DRW::AC1021 && mv > 3) { //2010+ duint32 hSize = buf->getRawLong32(); - endBitPos += 32; //start bit: + 4 hight size - DRW_DBG("\n2010+ & MV> 3, higth 32b: "); DRW_DBG(hSize); + endBitPos += 32; //start bit: + 4 height size + DRW_DBG("\n2010+ & MV> 3, height 32b: "); DRW_DBG(hSize); } //RLZ TODO add $ACADVER var & $DWGCODEPAGE & $MEASUREMENT //RLZ TODO EN 2000 falta $CELWEIGHT, $ENDCAPS, $EXTNAMES $JOINSTYLE $LWDISPLAY $PSTYLEMODE $TDUCREATE $TDUUPDATE $XEDIT @@ -1968,7 +1972,7 @@ bool DRW_Header::parseDwg(DRW::Version version, dwgBuffer *buf, dwgBuffer *hBbuf // vars["TDUSRTIMER"]=new DRW_Variant(40, buf->getBitLong());//RLZ: TODO convert to day.msec // vars["TDUSRTIMER"]=new DRW_Variant(40, buf->getBitLong());//RLZ: TODO convert to day.msec vars["CECOLOR"]=new DRW_Variant(62, buf->getCmColor(version));//RLZ: TODO read CMC or EMC color - dwgHandle HANDSEED = buf->getHandle();//allways present in data stream + dwgHandle HANDSEED = buf->getHandle();//always present in data stream DRW_DBG("\nHANDSEED: "); DRW_DBGHL(HANDSEED.code, HANDSEED.size, HANDSEED.ref); dwgHandle CLAYER = hBbuf->getHandle(); DRW_DBG("\nCLAYER: "); DRW_DBGHL(CLAYER.code, CLAYER.size, CLAYER.ref); @@ -2413,7 +2417,7 @@ bool DRW_Header::parseDwg(DRW::Version version, dwgBuffer *buf, dwgBuffer *hBbuf } } - buf->setPosition(size+16+4); //readed size +16 start sentinel + 4 size + buf->setPosition(size+16+4); //read size +16 start sentinel + 4 size if (version > DRW::AC1021 && mv > 3) { //2010+ buf->getRawLong32();//advance 4 bytes (hisize) } diff --git a/extlib/libdxfrw/drw_objects.cpp b/extlib/libdxfrw/drw_objects.cpp index e7a3e7b..e453a90 100644 --- a/extlib/libdxfrw/drw_objects.cpp +++ b/extlib/libdxfrw/drw_objects.cpp @@ -451,13 +451,13 @@ void DRW_LType::parseCode(int code, dxfReader *reader){ //! Update line type /*! -* Update the size and length of line type acording to the path +* Update the size and length of line type according to the path * @author Rallaz */ /*TODO: control max length permited */ void DRW_LType::update(){ double d =0; - size = path.size(); + size = (int)path.size(); for (int i = 0; i< size; i++){ d += fabs(path.at(i)); } diff --git a/extlib/libdxfrw/drw_objects.h b/extlib/libdxfrw/drw_objects.h index 07cba08..0a872d0 100644 --- a/extlib/libdxfrw/drw_objects.h +++ b/extlib/libdxfrw/drw_objects.h @@ -439,7 +439,7 @@ public: * bit 1 (1) show out of limits * bit 2 (2) adaptive grid * bit 3 (4) allow subdivision - * bit 4 (8) follow dinamic SCP + * bit 4 (8) follow dynamic SCP **/ }; diff --git a/extlib/libdxfrw/intern/drw_cptable932.h b/extlib/libdxfrw/intern/drw_cptable932.h index 0d9be10..a85f3ca 100644 --- a/extlib/libdxfrw/intern/drw_cptable932.h +++ b/extlib/libdxfrw/intern/drw_cptable932.h @@ -6,7 +6,7 @@ //first entry in this table are 0xA1 #define CPOFFSET932 0xFEC0 //#define CP1LENGHT932 63 -#define CPLENGHT932 7724 +#define CPLENGTH932 7724 #define NOTFOUND932 0x30FB //Table 932 one byte are diff --git a/extlib/libdxfrw/intern/drw_cptable936.h b/extlib/libdxfrw/intern/drw_cptable936.h index 4c1c141..9f5fb56 100644 --- a/extlib/libdxfrw/intern/drw_cptable936.h +++ b/extlib/libdxfrw/intern/drw_cptable936.h @@ -5,7 +5,7 @@ //first entry in this tables are 0x80 #define CPOFFSET936 0x80 -#define CPLENGHT936 21791 +#define CPLENGTH936 21791 #define NOTFOUND936 0x003F //Table 949 one byte diff --git a/extlib/libdxfrw/intern/drw_cptable949.h b/extlib/libdxfrw/intern/drw_cptable949.h index 3276b60..5364bea 100644 --- a/extlib/libdxfrw/intern/drw_cptable949.h +++ b/extlib/libdxfrw/intern/drw_cptable949.h @@ -5,7 +5,7 @@ //first entry in this table are 0x80 #define CPOFFSET949 0x80 -#define CPLENGHT949 17048 +#define CPLENGTH949 17048 #define NOTFOUND949 0x003F //Table 949 one byte diff --git a/extlib/libdxfrw/intern/drw_cptable950.h b/extlib/libdxfrw/intern/drw_cptable950.h index 2afb89d..3adc1e8 100644 --- a/extlib/libdxfrw/intern/drw_cptable950.h +++ b/extlib/libdxfrw/intern/drw_cptable950.h @@ -5,7 +5,7 @@ //first entry in this table are 0x80 #define CPOFFSET950 0x80 -#define CPLENGHT950 13503 +#define CPLENGTH950 13503 #define NOTFOUND950 0x003F //Table 950 one byte diff --git a/extlib/libdxfrw/intern/drw_cptables.h b/extlib/libdxfrw/intern/drw_cptables.h index 2f7d758..7217743 100644 --- a/extlib/libdxfrw/intern/drw_cptables.h +++ b/extlib/libdxfrw/intern/drw_cptables.h @@ -3,7 +3,7 @@ //first entry in all tables are 0x80 #define CPOFFSET 0x80 -#define CPLENGHTCOMMON 128 +#define CPLENGTHCOMMON 128 //Table 874 static const int DRW_Table874[] = { diff --git a/extlib/libdxfrw/intern/drw_textcodec.cpp b/extlib/libdxfrw/intern/drw_textcodec.cpp index 673c79c..39b2912 100644 --- a/extlib/libdxfrw/intern/drw_textcodec.cpp +++ b/extlib/libdxfrw/intern/drw_textcodec.cpp @@ -47,8 +47,9 @@ void DRW_TextCodec::setVersion(std::string *v, bool dxfFormat){ } else if (versionStr == "AC1012" || versionStr == "AC1014" || versionStr == "AC1015" || versionStr == "AC1018") { setVersion(DRW::AC1015, dxfFormat); + } else { + setVersion(DRW::AC1021, dxfFormat); } - setVersion(DRW::AC1021, dxfFormat); } void DRW_TextCodec::setCodePage(std::string *c, bool dxfFormat){ @@ -56,40 +57,40 @@ void DRW_TextCodec::setCodePage(std::string *c, bool dxfFormat){ delete conv; if (version == DRW::AC1009 || version == DRW::AC1015) { if (cp == "ANSI_874") - conv = new DRW_ConvTable(DRW_Table874, CPLENGHTCOMMON); + conv = new DRW_ConvTable(DRW_Table874, CPLENGTHCOMMON); else if (cp == "ANSI_932") conv = new DRW_Conv932Table(DRW_Table932, DRW_LeadTable932, - DRW_DoubleTable932, CPLENGHT932); + DRW_DoubleTable932, CPLENGTH932); else if (cp == "ANSI_936") conv = new DRW_ConvDBCSTable(DRW_Table936, DRW_LeadTable936, - DRW_DoubleTable936, CPLENGHT936); + DRW_DoubleTable936, CPLENGTH936); else if (cp == "ANSI_949") conv = new DRW_ConvDBCSTable(DRW_Table949, DRW_LeadTable949, - DRW_DoubleTable949, CPLENGHT949); + DRW_DoubleTable949, CPLENGTH949); else if (cp == "ANSI_950") conv = new DRW_ConvDBCSTable(DRW_Table950, DRW_LeadTable950, - DRW_DoubleTable950, CPLENGHT950); + DRW_DoubleTable950, CPLENGTH950); else if (cp == "ANSI_1250") - conv = new DRW_ConvTable(DRW_Table1250, CPLENGHTCOMMON); + conv = new DRW_ConvTable(DRW_Table1250, CPLENGTHCOMMON); else if (cp == "ANSI_1251") - conv = new DRW_ConvTable(DRW_Table1251, CPLENGHTCOMMON); + conv = new DRW_ConvTable(DRW_Table1251, CPLENGTHCOMMON); else if (cp == "ANSI_1253") - conv = new DRW_ConvTable(DRW_Table1253, CPLENGHTCOMMON); + conv = new DRW_ConvTable(DRW_Table1253, CPLENGTHCOMMON); else if (cp == "ANSI_1254") - conv = new DRW_ConvTable(DRW_Table1254, CPLENGHTCOMMON); + conv = new DRW_ConvTable(DRW_Table1254, CPLENGTHCOMMON); else if (cp == "ANSI_1255") - conv = new DRW_ConvTable(DRW_Table1255, CPLENGHTCOMMON); + conv = new DRW_ConvTable(DRW_Table1255, CPLENGTHCOMMON); else if (cp == "ANSI_1256") - conv = new DRW_ConvTable(DRW_Table1256, CPLENGHTCOMMON); + conv = new DRW_ConvTable(DRW_Table1256, CPLENGTHCOMMON); else if (cp == "ANSI_1257") - conv = new DRW_ConvTable(DRW_Table1257, CPLENGHTCOMMON); + conv = new DRW_ConvTable(DRW_Table1257, CPLENGTHCOMMON); else if (cp == "ANSI_1258") - conv = new DRW_ConvTable(DRW_Table1258, CPLENGHTCOMMON); + conv = new DRW_ConvTable(DRW_Table1258, CPLENGTHCOMMON); else if (cp == "UTF-8") { //DXF older than 2007 are write in win codepages cp = "ANSI_1252"; conv = new DRW_Converter(NULL, 0); } else - conv = new DRW_ConvTable(DRW_Table1252, CPLENGHTCOMMON); + conv = new DRW_ConvTable(DRW_Table1252, CPLENGTHCOMMON); } else { if (dxfFormat) conv = new DRW_Converter(NULL, 0);//utf16 to utf8 @@ -148,7 +149,7 @@ std::string DRW_ConvTable::fromUtf8(std::string *s) { j = i+l; i = j - 1; notFound = true; - for (int k=0; k0x390 && code<0x542) || (code>0x200F && code<0x9FA1) || code>0xF928 )) { - for (int k=0; k buffer; duint32 result =0; @@ -472,7 +472,7 @@ duint32 dwgBuffer::getUModularChar(){ return result; } -/**Reads modular int, char based, compresed form, little-endian order, returns a signed int (MC) **/ +/**Reads modular int, char based, compressed form, little-endian order, returns a signed int (MC) **/ dint32 dwgBuffer::getModularChar(){ bool negative = false; std::vector buffer; @@ -500,7 +500,7 @@ dint32 dwgBuffer::getModularChar(){ return result; } -/**Reads modular int, short based, compresed form, little-endian order, returns a unsigned int (MC) **/ +/**Reads modular int, short based, compressed form, little-endian order, returns a unsigned int (MC) **/ dint32 dwgBuffer::getModularShort(){ // bool negative = false; std::vector buffer; @@ -691,7 +691,7 @@ DRW_Coord dwgBuffer::getExtrusion(bool b_R2000_style) { return ext; } -/**Reads compresed Double with default (max. 64 + 2 bits) returns a floating point double of 64 bits (DD) **/ +/**Reads compressed Double with default (max. 64 + 2 bits) returns a floating point double of 64 bits (DD) **/ double dwgBuffer::getDefaultDouble(double d){ dint8 b = get2Bits(); if (b == 0) diff --git a/extlib/libdxfrw/intern/dwgreader.h b/extlib/libdxfrw/intern/dwgreader.h index 4e88932..fa84852 100644 --- a/extlib/libdxfrw/intern/dwgreader.h +++ b/extlib/libdxfrw/intern/dwgreader.h @@ -43,14 +43,14 @@ public: * address, address in file stream * dataSize, data size for this page * startOffset, start offset for this page - * cSize, compresed size of data - * uSize, uncompresed size of data + * cSize, compressed size of data + * uSize, uncompressed size of data * 2007: page Id, pageCount & pages * size, size in file * dataSize - * startOffset, start position in decompresed data stream - * cSize, compresed size of data - * uSize, uncompresed size of data + * startOffset, start position in decompressed data stream + * cSize, compressed size of data + * uSize, uncompressed size of data * address, address in file stream * */ class dwgPageInfo { @@ -65,25 +65,25 @@ public: duint64 size; //in file stream, for rd18, rd21 duint64 dataSize; //for rd18, rd21 duint32 startOffset; //for rd18, rd21 - duint64 cSize; //compresed page size, for rd21 - duint64 uSize; //uncompresed page size, for rd21 + duint64 cSize; //compressed page size, for rd21 + duint64 uSize; //uncompressed page size, for rd21 }; // sections of file /* 2000-: No pages, only section Id, size & address in file * 2004+: Id, Section Id - * size, total size of uncompresed data + * size, total size of uncompressed data * pageCount & pages, number of pages in section * maxSize, max decompressed Size per page - * compresed, (1 = no, 2 = yes, normally 2) + * compressed, (1 = no, 2 = yes, normally 2) * encrypted, (0 = no, 1 = yes, 2 = unknown) * name, read & stored but not used - * 2007: same as 2004+ except encoding, saved in compresed field + * 2007: same as 2004+ except encoding, saved in compressed field * */ class dwgSectionInfo { public: dwgSectionInfo(){ - compresed = 1;//1=no, 2=yes + compressed = 1;//1=no, 2=yes encrypted = 0;//??? pageCount = 0; Id=-1; @@ -91,7 +91,7 @@ public: ~dwgSectionInfo(){} dint32 Id; //section Id, 2000- rd15 rd18 std::string name; //section name rd18 - duint32 compresed;//is compresed? 1=no, 2=yes rd18, rd21(encoding) + duint32 compressed;//is compressed? 1=no, 2=yes rd18, rd21(encoding) duint32 encrypted;//encrypted (doc: 0=no, 1=yes, 2=unkn) on read: objects 0 and encrypted yes rd18 std::mappages;//index, size, offset duint64 size;//size of section, 2000- rd15, rd18, rd21 (data size) @@ -164,8 +164,8 @@ protected: public: std::mapObjectMap; - std::mapobjObjectMap; //stores the ojects & entities not read in readDwgEntities - std::mapremainingMap; //stores the ojects & entities not read in all proces, for debug only + std::mapobjObjectMap; //stores the objects & entities not read in readDwgEntities + std::mapremainingMap; //stores the objects & entities not read in all processes, for debug only std::map ltypemap; std::map layermap; std::map blockmap; diff --git a/extlib/libdxfrw/intern/dwgreader18.cpp b/extlib/libdxfrw/intern/dwgreader18.cpp index d91b92e..039c2c1 100644 --- a/extlib/libdxfrw/intern/dwgreader18.cpp +++ b/extlib/libdxfrw/intern/dwgreader18.cpp @@ -91,7 +91,7 @@ void dwgReader18::parseSysPage(duint8 *decompSec, duint32 decompSize){ } else { DRW_DBG(", "); j++; } } DRW_DBG("\n"); #endif - DRW_DBG("decompresing "); DRW_DBG(compSize); DRW_DBG(" bytes in "); DRW_DBG(decompSize); DRW_DBG(" bytes\n"); + DRW_DBG("decompressing "); DRW_DBG(compSize); DRW_DBG(" bytes in "); DRW_DBG(decompSize); DRW_DBG(" bytes\n"); dwgCompressor comp; comp.decompress18(tmpCompSec, decompSec, compSize, decompSize); #ifdef DRW_DBG_DUMP @@ -146,7 +146,7 @@ bool dwgReader18::parseDataPage(dwgSectionInfo si/*, duint8 *dData*/){ DRW_DBG("\n header checksum= "); DRW_DBGH(bufHdr.getRawLong32()); DRW_DBG("\n data checksum= "); DRW_DBGH(bufHdr.getRawLong32()); DRW_DBG("\n"); - //get compresed data + //get compressed data duint8 *cData = new duint8[pi.cSize]; if (!fileBuf->setPosition(pi.address+32)) return false; @@ -162,7 +162,7 @@ bool dwgReader18::parseDataPage(dwgSectionInfo si/*, duint8 *dData*/){ duint8* oData = objData + pi.startOffset; pi.uSize = si.maxSize; - DRW_DBG("decompresing "); DRW_DBG(pi.cSize); DRW_DBG(" bytes in "); DRW_DBG(pi.uSize); DRW_DBG(" bytes\n"); + DRW_DBG("decompressing "); DRW_DBG(pi.cSize); DRW_DBG(" bytes in "); DRW_DBG(pi.uSize); DRW_DBG(" bytes\n"); dwgCompressor comp; comp.decompress18(cData, oData, pi.cSize, pi.uSize); delete[]cData; @@ -177,7 +177,7 @@ bool dwgReader18::readMetaData() { if (! fileBuf->setPosition(11)) return false; maintenanceVersion = fileBuf->getRawChar8(); - DRW_DBG("maintenance verion= "); DRW_DBGH(maintenanceVersion); + DRW_DBG("maintenance version= "); DRW_DBGH(maintenanceVersion); DRW_DBG("\nbyte at 0x0C= "); DRW_DBGH(fileBuf->getRawChar8()); previewImagePos = fileBuf->getRawLong32(); //+ page header size (0x20). DRW_DBG("\npreviewImagePos (seekerImageData) = "); DRW_DBG(previewImagePos); @@ -304,7 +304,7 @@ bool dwgReader18::readFileHeader() { duint8 *tmpDecompSec = new duint8[decompSize]; parseSysPage(tmpDecompSec, decompSize); -//parses "Section page map" decompresed data +//parses "Section page map" decompressed data dwgBuffer buff2(tmpDecompSec, decompSize, &decoder); duint32 address = 0x100; //stores temporaly info of all pages: @@ -366,8 +366,8 @@ bool dwgReader18::readFileHeader() { secInfo.maxSize = buff3.getRawLong32(); DRW_DBG("\nMax Decompressed Size= "); DRW_DBGH(secInfo.maxSize); DRW_DBG("\nunknown long= "); DRW_DBGH(buff3.getRawLong32()); - secInfo.compresed = buff3.getRawLong32(); - DRW_DBG("\nis Compressed? 1:no, 2:yes= "); DRW_DBGH(secInfo.compresed); + secInfo.compressed = buff3.getRawLong32(); + DRW_DBG("\nis Compressed? 1:no, 2:yes= "); DRW_DBGH(secInfo.compressed); secInfo.Id = buff3.getRawLong32(); DRW_DBG("\nSection Id= "); DRW_DBGH(secInfo.Id); secInfo.encrypted = buff3.getRawLong32(); @@ -451,7 +451,7 @@ bool dwgReader18::readDwgClasses(){ DRW_DBG("\ndata size in bytes "); DRW_DBG(size); if (version > DRW::AC1021 && maintenanceVersion > 3) { //2010+ duint32 hSize = dataBuf.getRawLong32(); - DRW_DBG("\n2010+ & MV> 3, higth 32b: "); DRW_DBG(hSize); + DRW_DBG("\n2010+ & MV> 3, height 32b: "); DRW_DBG(hSize); } duint32 bitSize = 0; if (version > DRW::AC1021) {//2007+ diff --git a/extlib/libdxfrw/intern/dwgreader21.cpp b/extlib/libdxfrw/intern/dwgreader21.cpp index 9d4f23c..1a3af93 100644 --- a/extlib/libdxfrw/intern/dwgreader21.cpp +++ b/extlib/libdxfrw/intern/dwgreader21.cpp @@ -28,7 +28,7 @@ bool dwgReader21::readMetaData() { if (! fileBuf->setPosition(11)) return false; maintenanceVersion = fileBuf->getRawChar8(); - DRW_DBG("maintenance verion= "); DRW_DBGH(maintenanceVersion); + DRW_DBG("maintenance version= "); DRW_DBGH(maintenanceVersion); DRW_DBG("\nbyte at 0x0C= "); DRW_DBG(fileBuf->getRawChar8()); previewImagePos = fileBuf->getRawLong32(); DRW_DBG("previewImagePos (seekerImageData) = "); DRW_DBG(previewImagePos); @@ -110,7 +110,7 @@ bool dwgReader21::parseDataPage(dwgSectionInfo si, duint8 *dData){ dwgCompressor::decompress21(tmpPageRS, pageData, pi.cSize, pi.uSize); #ifdef DRW_DBG_DUMP - DRW_DBG("\n\nSection OBJECTS decompresed data=\n"); + DRW_DBG("\n\nSection OBJECTS decompressed data=\n"); for (unsigned int i=0, j=0; i< pi.uSize;i++) { DRW_DBGH( (unsigned char)pageData[i]); if (j == 7) { DRW_DBG("\n"); j = 0; @@ -161,7 +161,7 @@ bool dwgReader21::readFileHeader() { fileHdrData = new duint8[fileHdrDataLength]; fileHdrBuf.getBytes(fileHdrData, fileHdrDataLength); }else { - DRW_DBG("\ndwgReader21:: file header are compresed:\n"); + DRW_DBG("\ndwgReader21:: file header are compressed:\n"); duint8 *compByteStr = new duint8[fileHdrCompLength]; fileHdrBuf.getBytes(compByteStr, fileHdrCompLength); fileHdrData = new duint8[fileHdrDataLength]; @@ -281,8 +281,8 @@ bool dwgReader21::readFileHeader() { duint64 SectionNameLength = SectionsMapBuf.getRawLong64(); DRW_DBG("\nSectionNameLength = "); DRW_DBG(SectionNameLength); DRW_DBG("\nUnknown = "); DRW_DBGH(SectionsMapBuf.getRawLong64()); - secInfo.compresed = SectionsMapBuf.getRawLong64(); - DRW_DBG("\nEncoding (compresed) = "); DRW_DBGH(secInfo.compresed); + secInfo.compressed = SectionsMapBuf.getRawLong64(); + DRW_DBG("\nEncoding (compressed) = "); DRW_DBGH(secInfo.compressed); secInfo.pageCount = SectionsMapBuf.getRawLong64(); DRW_DBG("\nPage count= "); DRW_DBGH(secInfo.pageCount); secInfo.name = SectionsMapBuf.getUCSStr(SectionNameLength); diff --git a/extlib/libdxfrw/intern/dwgutil.cpp b/extlib/libdxfrw/intern/dwgutil.cpp index 10121cf..8fc6eb3 100644 --- a/extlib/libdxfrw/intern/dwgutil.cpp +++ b/extlib/libdxfrw/intern/dwgutil.cpp @@ -151,10 +151,10 @@ void dwgCompressor::decompress18(duint8 *cbuf, duint8 *dbuf, duint32 csize, duin duint32 compOffset; duint32 litCount; - pos=0; //current position in compresed buffer - rpos=0; //current position in resulting decompresed buffer + pos=0; //current position in compressed buffer + rpos=0; //current position in resulting decompressed buffer litCount = litLength18(); - //copy first lileral lenght + //copy first literal length for (duint32 i=0; i < litCount; ++i) { bufD[rpos++] = bufC[pos++]; } @@ -199,7 +199,7 @@ void dwgCompressor::decompress18(duint8 *cbuf, duint8 *dbuf, duint32 csize, duin DRW_DBG(pos);DRW_DBG(", Dpos: ");DRW_DBG(rpos);DRW_DBG("\n"); return; //fails, not valid } - //copy "compresed data", TODO Needed verify out of bounds + //copy "compressed data", TODO Needed verify out of bounds duint32 remaining = sizeD - (litCount+rpos); if (remaining < compBytes){ compBytes = remaining; @@ -209,7 +209,7 @@ void dwgCompressor::decompress18(duint8 *cbuf, duint8 *dbuf, duint32 csize, duin for (duint32 i=0, j= rpos - compOffset -1; i < compBytes; i++) { bufD[rpos++] = bufD[j++]; } - //copy "uncompresed data", TODO Needed verify out of bounds + //copy "uncompressed data", TODO Needed verify out of bounds for (duint32 i=0; i < litCount; i++) { bufD[rpos++] = bufC[pos++]; } @@ -274,7 +274,7 @@ void dwgCompressor::decompress21(duint8 *cbuf, duint8 *dbuf, duint32 csize, duin copyCompBytes21(cbuf, dbuf, length, srcIndex, dstIndex); srcIndex += length; dstIndex += length; - if (dstIndex >=dsize) break; //check if last chunk are compresed & terminate + if (dstIndex >=dsize) break; //check if last chunk are compressed & terminate length = 0; opCode = cbuf[srcIndex++]; diff --git a/extlib/libdxfrw/intern/dxfreader.h b/extlib/libdxfrw/intern/dxfreader.h index a0fa327..71329be 100644 --- a/extlib/libdxfrw/intern/dxfreader.h +++ b/extlib/libdxfrw/intern/dxfreader.h @@ -48,7 +48,7 @@ public: std::string getCodePage(){ return decoder.getCodePage();} protected: - virtual bool readCode(int *code) = 0; //return true if sucesful (not EOF) + virtual bool readCode(int *code) = 0; //return true if successful (not EOF) virtual bool readString(std::string *text) = 0; virtual bool readString() = 0; virtual bool readInt16() = 0; diff --git a/extlib/libdxfrw/libdxfrw.cpp b/extlib/libdxfrw/libdxfrw.cpp index 9eecea7..ac547ee 100644 --- a/extlib/libdxfrw/libdxfrw.cpp +++ b/extlib/libdxfrw/libdxfrw.cpp @@ -384,15 +384,15 @@ bool dxfRW::writeDimstyle(DRW_Dimstyle *ent){ } else writer->writeUtf8Caps(2, ent->name); writer->writeInt16(70, ent->flags); - if ( version == DRW::AC1009 || !(ent->dimpost.empty()) ) + if ( version <= DRW::AC1009 || !(ent->dimpost.empty()) ) writer->writeUtf8String(3, ent->dimpost); - if ( version == DRW::AC1009 || !(ent->dimapost.empty()) ) + if ( version <= DRW::AC1009 || !(ent->dimapost.empty()) ) writer->writeUtf8String(4, ent->dimapost); - if ( version == DRW::AC1009 || !(ent->dimblk.empty()) ) + if ( version <= DRW::AC1009 || !(ent->dimblk.empty()) ) writer->writeUtf8String(5, ent->dimblk); - if ( version == DRW::AC1009 || !(ent->dimblk1.empty()) ) + if ( version <= DRW::AC1009 || !(ent->dimblk1.empty()) ) writer->writeUtf8String(6, ent->dimblk1); - if ( version == DRW::AC1009 || !(ent->dimblk2.empty()) ) + if ( version <= DRW::AC1009 || !(ent->dimblk2.empty()) ) writer->writeUtf8String(7, ent->dimblk2); writer->writeDouble(40, ent->dimscale); writer->writeDouble(41, ent->dimasz); @@ -711,7 +711,7 @@ bool dxfRW::writeLWPolyline(DRW_LWPolyline *ent){ if (version > DRW::AC1009) { writer->writeString(100, "AcDbPolyline"); } - ent->vertexnum = ent->vertlist.size(); + ent->vertexnum = (int)ent->vertlist.size(); writer->writeInt32(90, ent->vertexnum); writer->writeInt16(70, ent->flags); writer->writeDouble(43, ent->width); @@ -739,11 +739,14 @@ bool dxfRW::writeLWPolyline(DRW_LWPolyline *ent){ bool dxfRW::writePolyline(DRW_Polyline *ent) { writer->writeString(0, "POLYLINE"); writeEntity(ent); + bool is3d = false; if (version > DRW::AC1009) { - if (ent->flags & 8 || ent->flags & 16) - writer->writeString(100, "AcDb2dPolyline"); - else + if (ent->flags & 8 || ent->flags & 16) { writer->writeString(100, "AcDb3dPolyline"); + is3d = true; + } else { + writer->writeString(100, "AcDb2dPolyline"); + } } else writer->writeInt16(66, 1); writer->writeDouble(10, 0.0); @@ -779,13 +782,18 @@ bool dxfRW::writePolyline(DRW_Polyline *ent) { writer->writeDouble(230, crd.z); } - int vertexnum = ent->vertlist.size(); - for (int i = 0; i< vertexnum; i++){ + size_t vertexnum = ent->vertlist.size(); + for (size_t i = 0; i < vertexnum; i++) { DRW_Vertex *v = ent->vertlist.at(i); writer->writeString(0, "VERTEX"); writeEntity(ent); if (version > DRW::AC1009) writer->writeString(100, "AcDbVertex"); + if(is3d) { + writer->writeString(100, "AcDb3dPolylineVertex"); + } else { + writer->writeString(100, "AcDb2dVertex"); + } if ( (v->flags & 128) && !(v->flags & 64) ) { writer->writeDouble(10, 0); writer->writeDouble(20, 0); @@ -880,7 +888,7 @@ bool dxfRW::writeHatch(DRW_Hatch *ent){ writer->writeString(2, ent->name); writer->writeInt16(70, ent->solid); writer->writeInt16(71, ent->associative); - ent->loopsnum = ent->looplist.size(); + ent->loopsnum = (int)ent->looplist.size(); writer->writeInt16(91, ent->loopsnum); //write paths data for (int i = 0; i< ent->loopsnum; i++){ @@ -1296,7 +1304,7 @@ bool dxfRW::writeBlock(DRW_Block *bk){ } writer->writeString(100, "AcDbEntity"); } - writer->writeString(8, "0"); + writer->writeString(8, bk->layer); if (version > DRW::AC1009) { writer->writeString(100, "AcDbBlockEnd"); } @@ -1311,7 +1319,7 @@ bool dxfRW::writeBlock(DRW_Block *bk){ } writer->writeString(100, "AcDbEntity"); } - writer->writeString(8, "0"); + writer->writeString(8, bk->layer); if (version > DRW::AC1009) { writer->writeString(100, "AcDbBlockBegin"); writer->writeUtf8String(2, bk->name); @@ -1415,7 +1423,7 @@ bool dxfRW::writeTables() { writer->writeInt16(72, 65); writer->writeInt16(73, 0); writer->writeDouble(40, 0.0); -//Aplication linetypes +//Application linetypes iface->writeLTypes(); writer->writeString(0, "ENDTAB"); /*** LAYER ***/ @@ -1431,7 +1439,7 @@ bool dxfRW::writeTables() { writer->writeInt16(70, 1); //end table def wlayer0 =false; iface->writeLayers(); - if (!wlayer0) { + if (!wlayer0 && version > DRW::AC1009) { DRW_Layer lay0; lay0.name = "0"; writeLayer(&lay0); @@ -1566,7 +1574,7 @@ bool dxfRW::writeTables() { writer->writeInt16(281, 0); } } - /* allways call writeBlockRecords to iface for prepare unnamed blocks */ + /* always call writeBlockRecords to iface for prepare unnamed blocks */ iface->writeBlockRecords(); if (version > DRW::AC1009) { writer->writeString(0, "ENDTAB"); diff --git a/extlib/mimalloc/.gitattributes b/extlib/mimalloc/.gitattributes new file mode 100644 index 0000000..acdbdbf --- /dev/null +++ b/extlib/mimalloc/.gitattributes @@ -0,0 +1,10 @@ +# default behavior is to always use unix style line endings +* text eol=lf +*.png binary +*.pdn binary +*.sln binary +*.suo binary +*.vcproj binary +*.patch binary +*.dll binary +*.lib binary diff --git a/extlib/mimalloc/.gitignore b/extlib/mimalloc/.gitignore new file mode 100644 index 0000000..3639d32 --- /dev/null +++ b/extlib/mimalloc/.gitignore @@ -0,0 +1,8 @@ +ide/vs20??/*.db +ide/vs20??/*.opendb +ide/vs20??/*.user +ide/vs20??/*.vcxproj.filters +ide/vs20??/.vs +out/ +docs/ +*.zip diff --git a/extlib/mimalloc/CMakeLists.txt b/extlib/mimalloc/CMakeLists.txt new file mode 100644 index 0000000..37616eb --- /dev/null +++ b/extlib/mimalloc/CMakeLists.txt @@ -0,0 +1,361 @@ +cmake_minimum_required(VERSION 3.0) +project(libmimalloc C CXX) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +option(MI_SECURE "Use full security mitigations (like guard pages, allocation randomization, double-free mitigation, and free-list corruption detection)" OFF) +option(MI_DEBUG_FULL "Use full internal heap invariant checking in DEBUG mode (expensive)" OFF) +option(MI_PADDING "Enable padding to detect heap block overflow (used only in DEBUG mode)" ON) +option(MI_OVERRIDE "Override the standard malloc interface (e.g. define entry points for malloc() etc)" ON) +option(MI_XMALLOC "Enable abort() call on memory allocation failure by default" OFF) +option(MI_SHOW_ERRORS "Show error and warning messages by default (only enabled by default in DEBUG mode)" OFF) +option(MI_USE_CXX "Use the C++ compiler to compile the library (instead of the C compiler)" OFF) +option(MI_SEE_ASM "Generate assembly files" OFF) +option(MI_INTERPOSE "Use interpose to override standard malloc on macOS" ON) +option(MI_OSX_ZONE "Use malloc zone to override standard malloc on macOS" OFF) # enables interpose as well +option(MI_LOCAL_DYNAMIC_TLS "Use slightly slower, dlopen-compatible TLS mechanism (Unix)" OFF) +option(MI_BUILD_SHARED "Build shared library" ON) +option(MI_BUILD_STATIC "Build static library" ON) +option(MI_BUILD_OBJECT "Build object library" ON) +option(MI_BUILD_TESTS "Build test executables" ON) +option(MI_DEBUG_TSAN "Build with thread sanitizer (needs clang)" OFF) +option(MI_DEBUG_UBSAN "Build with undefined-behavior sanitizer (needs clang++)" OFF) +option(MI_CHECK_FULL "Use full internal invariant checking in DEBUG mode (deprecated, use MI_DEBUG_FULL instead)" OFF) + +include("cmake/mimalloc-config-version.cmake") + +set(mi_sources + src/stats.c + src/random.c + src/os.c + src/arena.c + src/region.c + src/segment.c + src/page.c + src/alloc.c + src/alloc-aligned.c + src/alloc-posix.c + src/heap.c + src/options.c + src/init.c) + +# ----------------------------------------------------------------------------- +# Converience: set default build type depending on the build directory +# ----------------------------------------------------------------------------- + +if (NOT CMAKE_BUILD_TYPE) + if ("${CMAKE_BINARY_DIR}" MATCHES ".*(D|d)ebug$" OR MI_DEBUG_FULL MATCHES "ON") + message(STATUS "No build type selected, default to: Debug") + set(CMAKE_BUILD_TYPE "Debug") + else() + message(STATUS "No build type selected, default to: Release") + set(CMAKE_BUILD_TYPE "Release") + endif() +endif() + +if("${CMAKE_BINARY_DIR}" MATCHES ".*(S|s)ecure$") + message(STATUS "Default to secure build") + set(MI_SECURE "ON") +endif() + +# ----------------------------------------------------------------------------- +# Process options +# ----------------------------------------------------------------------------- + +if(CMAKE_C_COMPILER_ID MATCHES "MSVC|Intel") + set(MI_USE_CXX "ON") +endif() + +if(MI_OVERRIDE MATCHES "ON") + message(STATUS "Override standard malloc (MI_OVERRIDE=ON)") + if(APPLE) + if(MI_OSX_ZONE MATCHES "ON") + # use zone's on macOS + message(STATUS " Use malloc zone to override malloc (MI_OSX_ZONE=ON)") + list(APPEND mi_sources src/alloc-override-osx.c) + list(APPEND mi_defines MI_OSX_ZONE=1) + if(NOT MI_INTERPOSE MATCHES "ON") + message(STATUS " (enabling INTERPOSE as well since zone's require this)") + set(MI_INTERPOSE "ON") + endif() + endif() + if(MI_INTERPOSE MATCHES "ON") + # use interpose on macOS + message(STATUS " Use interpose to override malloc (MI_INTERPOSE=ON)") + list(APPEND mi_defines MI_INTERPOSE) + endif() + endif() +endif() + +if(MI_SECURE MATCHES "ON") + message(STATUS "Set full secure build (MI_SECURE=ON)") + list(APPEND mi_defines MI_SECURE=4) +endif() + +if(MI_SEE_ASM MATCHES "ON") + message(STATUS "Generate assembly listings (MI_SEE_ASM=ON)") + list(APPEND mi_cflags -save-temps) +endif() + +if(MI_CHECK_FULL MATCHES "ON") + message(STATUS "The MI_CHECK_FULL option is deprecated, use MI_DEBUG_FULL instead") + set(MI_DEBUG_FULL "ON") +endif() + +if(MI_DEBUG_FULL MATCHES "ON") + message(STATUS "Set debug level to full internal invariant checking (MI_DEBUG_FULL=ON)") + list(APPEND mi_defines MI_DEBUG=3) # full invariant checking +endif() + +if(MI_PADDING MATCHES "OFF") + message(STATUS "Disable padding of heap blocks in debug mode (MI_PADDING=OFF)") + list(APPEND mi_defines MI_PADDING=0) +endif() + +if(MI_XMALLOC MATCHES "ON") + message(STATUS "Enable abort() calls on memory allocation failure (MI_XMALLOC=ON)") + list(APPEND mi_defines MI_XMALLOC=1) +endif() + +if(MI_SHOW_ERRORS MATCHES "ON") + message(STATUS "Enable printing of error and warning messages by default (MI_SHOW_ERRORS=ON)") + list(APPEND mi_defines MI_SHOW_ERRORS=1) +endif() + +if(MI_DEBUG_TSAN MATCHES "ON") + if(CMAKE_C_COMPILER_ID MATCHES "Clang") + message(STATUS "Build with thread sanitizer (MI_DEBUG_TSAN=ON)") + list(APPEND mi_cflags -fsanitize=thread -g -O1) + list(APPEND CMAKE_EXE_LINKER_FLAGS -fsanitize=thread) + else() + message(WARNING "Can only use thread sanitizer with clang (MI_DEBUG_TSAN=ON but ignored)") + endif() +endif() + +if(MI_DEBUG_UBSAN MATCHES "ON") + if(CMAKE_BUILD_TYPE MATCHES "Debug") + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(STATUS "Build with undefined-behavior sanitizer (MI_DEBUG_UBSAN=ON)") + list(APPEND mi_cflags -fsanitize=undefined -g) + list(APPEND CMAKE_EXE_LINKER_FLAGS -fsanitize=undefined) + if (MI_USE_CXX MATCHES "OFF") + message(STATUS "(switch to use C++ due to MI_DEBUG_UBSAN)") + set(MI_USE_CXX "ON") + endif() + else() + message(WARNING "Can only use undefined-behavior sanitizer with clang++ (MI_DEBUG_UBSAN=ON but ignored)") + endif() + else() + message(WARNING "Can only use thread sanitizer with a debug build (CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE})") + endif() +endif() + +if(MI_USE_CXX MATCHES "ON") + message(STATUS "Use the C++ compiler to compile (MI_USE_CXX=ON)") + set_source_files_properties(${mi_sources} PROPERTIES LANGUAGE CXX ) + set_source_files_properties(src/static.c test/test-api.c test/test-stress PROPERTIES LANGUAGE CXX ) + if(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang|Clang") + list(APPEND mi_cflags -Wno-deprecated) + endif() + if(CMAKE_CXX_COMPILER_ID MATCHES "Intel") + list(APPEND mi_cflags -Kc++) + endif() +endif() + +# Compiler flags +if(CMAKE_C_COMPILER_ID MATCHES "AppleClang|Clang|GNU") + list(APPEND mi_cflags -Wall -Wextra -Wno-unknown-pragmas -fvisibility=hidden) + if(CMAKE_C_COMPILER_ID MATCHES "GNU") + list(APPEND mi_cflags -Wno-invalid-memory-model) + endif() +endif() + +if(CMAKE_C_COMPILER_ID MATCHES "Intel") + list(APPEND mi_cflags -Wall -fvisibility=hidden) +endif() + +if(CMAKE_C_COMPILER_ID MATCHES "AppleClang|Clang|GNU|Intel" AND NOT CMAKE_SYSTEM_NAME MATCHES "Haiku") + if(MI_LOCAL_DYNAMIC_TLS MATCHES "ON") + list(APPEND mi_cflags -ftls-model=local-dynamic) + else() + list(APPEND mi_cflags -ftls-model=initial-exec) + endif() +endif() + +# Architecture flags +if(${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "arm") + list(APPEND mi_cflags -march=native) +endif() + +# extra needed libraries +if(WIN32) + list(APPEND mi_libraries psapi shell32 user32 bcrypt) +else() + if(NOT ${CMAKE_C_COMPILER} MATCHES "android") + list(APPEND mi_libraries pthread) + find_library(LIBRT rt) + if(LIBRT) + list(APPEND mi_libraries ${LIBRT}) + endif() + endif() +endif() + +# ----------------------------------------------------------------------------- +# Install and output names +# ----------------------------------------------------------------------------- + +set(mi_install_dir "${CMAKE_INSTALL_PREFIX}/lib/mimalloc-${mi_version}") +if(MI_SECURE MATCHES "ON") + set(mi_basename "mimalloc-secure") +else() + set(mi_basename "mimalloc") +endif() +string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LC) +if(NOT(CMAKE_BUILD_TYPE_LC MATCHES "^(release|relwithdebinfo|minsizerel)$")) + set(mi_basename "${mi_basename}-${CMAKE_BUILD_TYPE_LC}") #append build type (e.g. -debug) if not a release version +endif() +if(MI_BUILD_SHARED) + list(APPEND mi_build_targets "shared") +endif() +if(MI_BUILD_STATIC) + list(APPEND mi_build_targets "static") +endif() +if(MI_BUILD_OBJECT) + list(APPEND mi_build_targets "object") +endif() +if(MI_BUILD_TESTS) + list(APPEND mi_build_targets "tests") +endif() +message(STATUS "") +message(STATUS "Library base name: ${mi_basename}") +message(STATUS "Build type : ${CMAKE_BUILD_TYPE_LC}") +message(STATUS "Install directory: ${mi_install_dir}") +message(STATUS "Build targets : ${mi_build_targets}") +message(STATUS "") + +# ----------------------------------------------------------------------------- +# Main targets +# ----------------------------------------------------------------------------- + +# shared library +if(MI_BUILD_SHARED) + add_library(mimalloc SHARED ${mi_sources}) + set_target_properties(mimalloc PROPERTIES VERSION ${mi_version} OUTPUT_NAME ${mi_basename} ) + target_compile_definitions(mimalloc PRIVATE ${mi_defines} MI_SHARED_LIB MI_SHARED_LIB_EXPORT) + target_compile_options(mimalloc PRIVATE ${mi_cflags}) + target_link_libraries(mimalloc PUBLIC ${mi_libraries}) + target_include_directories(mimalloc PUBLIC + $ + $ + ) + if(WIN32) + # On windows copy the mimalloc redirection dll too. + target_link_libraries(mimalloc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bin/mimalloc-redirect.lib) + add_custom_command(TARGET mimalloc POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/bin/mimalloc-redirect.dll" $ + COMMENT "Copy mimalloc-redirect.dll to output directory") + endif() + + install(TARGETS mimalloc EXPORT mimalloc DESTINATION ${mi_install_dir} LIBRARY) + install(EXPORT mimalloc DESTINATION ${mi_install_dir}/cmake) +endif() + +# static library +if (MI_BUILD_STATIC) + add_library(mimalloc-static STATIC ${mi_sources}) + set_property(TARGET mimalloc-static PROPERTY POSITION_INDEPENDENT_CODE ON) + target_compile_definitions(mimalloc-static PRIVATE ${mi_defines} MI_STATIC_LIB) + target_compile_options(mimalloc-static PRIVATE ${mi_cflags}) + target_link_libraries(mimalloc-static PUBLIC ${mi_libraries}) + target_include_directories(mimalloc-static PUBLIC + $ + $ + ) + if(WIN32) + # When building both static and shared libraries on Windows, a static library should use a + # different output name to avoid the conflict with the import library of a shared one. + string(REPLACE "mimalloc" "mimalloc-static" mi_output_name ${mi_basename}) + set_target_properties(mimalloc-static PROPERTIES OUTPUT_NAME ${mi_output_name}) + else() + set_target_properties(mimalloc-static PROPERTIES OUTPUT_NAME ${mi_basename}) + endif() + + install(TARGETS mimalloc-static EXPORT mimalloc DESTINATION ${mi_install_dir}) +endif() + +# install include files +install(FILES include/mimalloc.h DESTINATION ${mi_install_dir}/include) +install(FILES include/mimalloc-override.h DESTINATION ${mi_install_dir}/include) +install(FILES include/mimalloc-new-delete.h DESTINATION ${mi_install_dir}/include) +install(FILES cmake/mimalloc-config.cmake DESTINATION ${mi_install_dir}/cmake) +install(FILES cmake/mimalloc-config-version.cmake DESTINATION ${mi_install_dir}/cmake) + +if(NOT WIN32 AND MI_BUILD_SHARED) + # install a symlink in the /usr/local/lib to the versioned library + set(mi_symlink "${CMAKE_SHARED_MODULE_PREFIX}${mi_basename}${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(mi_soname "mimalloc-${mi_version}/${mi_symlink}.${mi_version}") + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${mi_soname} ${mi_symlink} WORKING_DIRECTORY ${mi_install_dir}/..)") + install(CODE "MESSAGE(\"-- Symbolic link: ${CMAKE_INSTALL_PREFIX}/lib/${mi_symlink} -> ${mi_soname}\")") +endif() + +# single object file for more predictable static overriding +if (MI_BUILD_OBJECT) + add_library(mimalloc-obj OBJECT src/static.c) + set_property(TARGET mimalloc-obj PROPERTY POSITION_INDEPENDENT_CODE ON) + target_compile_definitions(mimalloc-obj PRIVATE ${mi_defines}) + target_compile_options(mimalloc-obj PRIVATE ${mi_cflags}) + target_include_directories(mimalloc-obj PUBLIC + $ + $ + ) + + # the following seems to lead to cmake warnings/errors on some systems, disable for now :-( + # install(TARGETS mimalloc-obj EXPORT mimalloc DESTINATION ${mi_install_dir}) + + # the FILES expression can also be: $ + # but that fails cmake versions less than 3.10 so we leave it as is for now + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/mimalloc-obj.dir/src/static.c${CMAKE_C_OUTPUT_EXTENSION} + DESTINATION ${mi_install_dir} + RENAME ${mi_basename}${CMAKE_C_OUTPUT_EXTENSION} ) +endif() + +# ----------------------------------------------------------------------------- +# API surface testing +# ----------------------------------------------------------------------------- + +if (MI_BUILD_TESTS MATCHES "ON") + add_executable(mimalloc-test-api test/test-api.c) + target_compile_definitions(mimalloc-test-api PRIVATE ${mi_defines}) + target_compile_options(mimalloc-test-api PRIVATE ${mi_cflags}) + target_include_directories(mimalloc-test-api PRIVATE include) + target_link_libraries(mimalloc-test-api PRIVATE mimalloc-static ${mi_libraries}) + + add_executable(mimalloc-test-stress test/test-stress.c) + target_compile_definitions(mimalloc-test-stress PRIVATE ${mi_defines}) + target_compile_options(mimalloc-test-stress PRIVATE ${mi_cflags}) + target_include_directories(mimalloc-test-stress PRIVATE include) + target_link_libraries(mimalloc-test-stress PRIVATE mimalloc ${mi_libraries}) + + enable_testing() + add_test(test_api, mimalloc-test-api) + add_test(test_stress, mimalloc-test-stress) +endif() + +# ----------------------------------------------------------------------------- +# Set override properties +# ----------------------------------------------------------------------------- +if (MI_OVERRIDE MATCHES "ON") + if (MI_BUILD_SHARED) + target_compile_definitions(mimalloc PRIVATE MI_MALLOC_OVERRIDE) + endif() + if(NOT WIN32) + # It is only possible to override malloc on Windows when building as a DLL. + if (MI_BUILD_STATIC) + target_compile_definitions(mimalloc-static PRIVATE MI_MALLOC_OVERRIDE) + endif() + if (MI_BUILD_OBJECT) + target_compile_definitions(mimalloc-obj PRIVATE MI_MALLOC_OVERRIDE) + endif() + endif() +endif() diff --git a/extlib/mimalloc/LICENSE b/extlib/mimalloc/LICENSE new file mode 100644 index 0000000..4151dbe --- /dev/null +++ b/extlib/mimalloc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Microsoft Corporation, Daan Leijen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extlib/mimalloc/azure-pipelines.yml b/extlib/mimalloc/azure-pipelines.yml new file mode 100644 index 0000000..c81e31b --- /dev/null +++ b/extlib/mimalloc/azure-pipelines.yml @@ -0,0 +1,131 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +trigger: +- master +- dev + +jobs: +- job: + displayName: Windows + pool: + vmImage: + windows-2019 + strategy: + matrix: + Debug: + BuildType: debug + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON + MSBuildConfiguration: Debug + Release: + BuildType: release + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release + MSBuildConfiguration: Release + Secure: + BuildType: secure + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON + MSBuildConfiguration: Release + steps: + - task: CMake@1 + inputs: + workingDirectory: $(BuildType) + cmakeArgs: .. $(cmakeExtraArgs) + - task: MSBuild@1 + inputs: + solution: $(BuildType)/libmimalloc.sln + configuration: '$(MSBuildConfiguration)' + - script: | + cd $(BuildType) + ctest + displayName: CTest +# - upload: $(Build.SourcesDirectory)/$(BuildType) +# artifact: mimalloc-windows-$(BuildType) + +- job: + displayName: Linux + pool: + vmImage: + ubuntu-16.04 + strategy: + matrix: + Debug: + CC: gcc + CXX: g++ + BuildType: debug + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON + Release: + CC: gcc + CXX: g++ + BuildType: release + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release + Secure: + CC: gcc + CXX: g++ + BuildType: secure + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON + Debug++: + CC: gcc + CXX: g++ + BuildType: debug-cxx + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_USE_CXX=ON + Debug Clang: + CC: clang + CXX: clang++ + BuildType: debug-clang + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON + Release Clang: + CC: clang + CXX: clang++ + BuildType: release-clang + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release + Secure Clang: + CC: clang + CXX: clang++ + BuildType: secure-clang + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON + Debug++ Clang: + CC: clang + CXX: clang++ + BuildType: debug-clang-cxx + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_USE_CXX=ON + steps: + - task: CMake@1 + inputs: + workingDirectory: $(BuildType) + cmakeArgs: .. $(cmakeExtraArgs) + - script: make -j$(nproc) -C $(BuildType) + displayName: Make + - script: make test -C $(BuildType) + displayName: CTest +# - upload: $(Build.SourcesDirectory)/$(BuildType) +# artifact: mimalloc-ubuntu-$(BuildType) + +- job: + displayName: macOS + pool: + vmImage: + macOS-10.14 + strategy: + matrix: + Debug: + BuildType: debug + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON + Release: + BuildType: release + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release + Secure: + BuildType: secure + cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON + steps: + - task: CMake@1 + inputs: + workingDirectory: $(BuildType) + cmakeArgs: .. $(cmakeExtraArgs) + - script: make -j$(sysctl -n hw.ncpu) -C $(BuildType) + displayName: Make + - script: make test -C $(BuildType) + displayName: CTest +# - upload: $(Build.SourcesDirectory)/$(BuildType) +# artifact: mimalloc-macos-$(BuildType) diff --git a/extlib/mimalloc/bin/mimalloc-redirect.dll b/extlib/mimalloc/bin/mimalloc-redirect.dll new file mode 100644 index 0000000000000000000000000000000000000000..b7bf1d099681fc73d15ee8dd1b86a48de0aca1d1 GIT binary patch literal 55808 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 literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/cmake/mimalloc-config-version.cmake b/extlib/mimalloc/cmake/mimalloc-config-version.cmake new file mode 100644 index 0000000..6454d91 --- /dev/null +++ b/extlib/mimalloc/cmake/mimalloc-config-version.cmake @@ -0,0 +1,16 @@ +set(mi_version_major 1) +set(mi_version_minor 6) +set(mi_version ${mi_version_major}.${mi_version_minor}) + +set(PACKAGE_VERSION ${mi_version}) +if("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL "${mi_version_major}") + if ("${PACKAGE_FIND_VERSION_MINOR}" EQUAL "${mi_version_minor}") + set(PACKAGE_VERSION_EXACT TRUE) + elseif("${PACKAGE_FIND_VERSION_MINOR}" LESS "${mi_version_minor}") + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_UNSUITABLE TRUE) + endif() +else() + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/extlib/mimalloc/cmake/mimalloc-config.cmake b/extlib/mimalloc/cmake/mimalloc-config.cmake new file mode 100644 index 0000000..12da076 --- /dev/null +++ b/extlib/mimalloc/cmake/mimalloc-config.cmake @@ -0,0 +1,2 @@ +include(${CMAKE_CURRENT_LIST_DIR}/mimalloc.cmake) +get_filename_component(MIMALLOC_TARGET_DIR "${CMAKE_CURRENT_LIST_DIR}" PATH) diff --git a/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-a.svg b/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-a.svg new file mode 100644 index 0000000..0e55093 --- /dev/null +++ b/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-a.svg @@ -0,0 +1,886 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-b.svg b/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-b.svg new file mode 100644 index 0000000..22bfa5c --- /dev/null +++ b/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-b.svg @@ -0,0 +1,1184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-rss-a.svg b/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-rss-a.svg new file mode 100644 index 0000000..6b15ebe --- /dev/null +++ b/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-rss-a.svg @@ -0,0 +1,756 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-rss-b.svg b/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-rss-b.svg new file mode 100644 index 0000000..e3eb774 --- /dev/null +++ b/extlib/mimalloc/doc/bench-c5-18xlarge-2020-01-20-rss-b.svg @@ -0,0 +1,1027 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-r5a-1.svg b/extlib/mimalloc/doc/bench-r5a-1.svg new file mode 100644 index 0000000..127d6de --- /dev/null +++ b/extlib/mimalloc/doc/bench-r5a-1.svg @@ -0,0 +1,768 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-r5a-12xlarge-2020-01-16-a.svg b/extlib/mimalloc/doc/bench-r5a-12xlarge-2020-01-16-a.svg new file mode 100644 index 0000000..b110ff4 --- /dev/null +++ b/extlib/mimalloc/doc/bench-r5a-12xlarge-2020-01-16-a.svg @@ -0,0 +1,867 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-r5a-12xlarge-2020-01-16-b.svg b/extlib/mimalloc/doc/bench-r5a-12xlarge-2020-01-16-b.svg new file mode 100644 index 0000000..f7a3287 --- /dev/null +++ b/extlib/mimalloc/doc/bench-r5a-12xlarge-2020-01-16-b.svg @@ -0,0 +1,1156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-r5a-2.svg b/extlib/mimalloc/doc/bench-r5a-2.svg new file mode 100644 index 0000000..8b7b2da --- /dev/null +++ b/extlib/mimalloc/doc/bench-r5a-2.svg @@ -0,0 +1,982 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-r5a-rss-1.svg b/extlib/mimalloc/doc/bench-r5a-rss-1.svg new file mode 100644 index 0000000..1c7f856 --- /dev/null +++ b/extlib/mimalloc/doc/bench-r5a-rss-1.svg @@ -0,0 +1,682 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-r5a-rss-2.svg b/extlib/mimalloc/doc/bench-r5a-rss-2.svg new file mode 100644 index 0000000..e819884 --- /dev/null +++ b/extlib/mimalloc/doc/bench-r5a-rss-2.svg @@ -0,0 +1,853 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-spec-rss.svg b/extlib/mimalloc/doc/bench-spec-rss.svg new file mode 100644 index 0000000..2c93616 --- /dev/null +++ b/extlib/mimalloc/doc/bench-spec-rss.svg @@ -0,0 +1,713 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-spec.svg b/extlib/mimalloc/doc/bench-spec.svg new file mode 100644 index 0000000..af2b41b --- /dev/null +++ b/extlib/mimalloc/doc/bench-spec.svg @@ -0,0 +1,713 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-z4-1.svg b/extlib/mimalloc/doc/bench-z4-1.svg new file mode 100644 index 0000000..dacd8ab --- /dev/null +++ b/extlib/mimalloc/doc/bench-z4-1.svg @@ -0,0 +1,890 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-z4-2.svg b/extlib/mimalloc/doc/bench-z4-2.svg new file mode 100644 index 0000000..9990cdc --- /dev/null +++ b/extlib/mimalloc/doc/bench-z4-2.svg @@ -0,0 +1,1146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-z4-rss-1.svg b/extlib/mimalloc/doc/bench-z4-rss-1.svg new file mode 100644 index 0000000..891f7d6 --- /dev/null +++ b/extlib/mimalloc/doc/bench-z4-rss-1.svg @@ -0,0 +1,796 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/bench-z4-rss-2.svg b/extlib/mimalloc/doc/bench-z4-rss-2.svg new file mode 100644 index 0000000..f426537 --- /dev/null +++ b/extlib/mimalloc/doc/bench-z4-rss-2.svg @@ -0,0 +1,974 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/doc/doxyfile b/extlib/mimalloc/doc/doxyfile new file mode 100644 index 0000000..6c1e30a --- /dev/null +++ b/extlib/mimalloc/doc/doxyfile @@ -0,0 +1,2564 @@ +# Doxyfile 1.8.15 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = mi-malloc + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = 1.6 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = mimalloc-logo.svg + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = .. + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is +# Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = YES + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = YES + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 0 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = NO + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = mimalloc-doc.h + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files +# were built. This is equivalent to specifying the "-p" option to a clang tool, +# such as clang-check. These options will then be passed to the parser. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = docs + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = mimalloc-doxygen.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 189 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 12 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 240 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via Javascript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have Javascript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: https://developer.apple.com/xcode/), introduced with OSX +# 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = YES + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 180 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /

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 bMI!${0bO36huu=@00000NkvXXu0mjfmW0V> literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/doc/mimalloc-logo.svg b/extlib/mimalloc/doc/mimalloc-logo.svg new file mode 100644 index 0000000..672c7e4 --- /dev/null +++ b/extlib/mimalloc/doc/mimalloc-logo.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extlib/mimalloc/docs/annotated.html b/extlib/mimalloc/docs/annotated.html new file mode 100644 index 0000000..feba243 --- /dev/null +++ b/extlib/mimalloc/docs/annotated.html @@ -0,0 +1,122 @@ + + + + + + + +mi-malloc: Data Structures + + + + + + + + + + + + + + + + +

+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
Data Structures
+
+
+
Here are the data structures with brief descriptions:
+ + + +
 Cmi_heap_area_tAn area of heap space contains blocks of a single size
 Cmi_stl_allocatorstd::allocator implementation for mimalloc for use in STL containers
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/annotated_dup.js b/extlib/mimalloc/docs/annotated_dup.js new file mode 100644 index 0000000..6722912 --- /dev/null +++ b/extlib/mimalloc/docs/annotated_dup.js @@ -0,0 +1,5 @@ +var annotated_dup = +[ + [ "mi_heap_area_t", "group__analysis.html#structmi__heap__area__t", "group__analysis_structmi__heap__area__t" ], + [ "mi_stl_allocator", "group__cpp.html#structmi__stl__allocator", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/bc_s.png b/extlib/mimalloc/docs/bc_s.png new file mode 100644 index 0000000000000000000000000000000000000000..e8fb7361df0aecebbbdf1998e88559dd8d74d600 GIT binary patch literal 657 zcmV;C0&e|@P)2Z5*uSELZGXPAbKZc(oFC#9KMfp?z!LGd*__<1JJpPmkwfy1Yi?N z8yi0XKnB9e^FfNzDm5gj?6li!03ZX(ou5mRB>6fJj{L@JezREkBNI&L&x(F+G88tE z6!oB7EW8;ODEt1|e&@%sljCmVSSCQ(*}Ul02%)`TG)7>{RZ5?}3fk@;pj-AzV`Jg8_J$+{Q%2zI)_tJX(N^NWFX-|;MWQPSg zN~>Mv1>vw(sd)b<5N%WN`;Vzq>Ra{d=eH;^l?DJA7L7*Z%?Z~2tzN5MLGe@y0A!#P z3Zm`t`4srk2^3Ez0RTq%#A2Pu8BC`4D0Z^fNdS;xUaQmHWgU(dVYS%+07m%`7tDnp z`T<2w!~sACgIIRn+xtrag<{76K!zoQ(Rgi)VYYW0jRh2pL;*mCirwX+l}gq1=1$`s znhb}BH?tZI`Er6~e>Hb^dUt?76dLAp-2UR+<@tM34t#;2L^Dow@eqp~m-2*}VWi`4 z(0baTCdu_yv-x5$54faK{MBk>DO}#G?bhDM!PuM3X3^(WYW0GSrVsJvG5{Qb^9UkW zanp);x|aIu0g>C|8AnLb=$D-9w7+)&;0Soc?uVkWLMl7G;^KK2B{<#OHa@;}DxH~f rx;YpnVIE@^acjH#oz5cwfKmPfrlXi_XgDXf00000NkvXXu0mjfJ$f&F literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/bdwn.png b/extlib/mimalloc/docs/bdwn.png new file mode 100644 index 0000000000000000000000000000000000000000..bbbba4e6c24ad6c28f351317762b11beca630e54 GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)H!3HEvS)PKZP)`@fkP61PbMNvtDDbq{m+~(z z{?RmE_)9=MwGhaw6{(bkg-~;aeiB+NuI!|6-y6tqaZr1ct0nOWc qtrWYiq?OC8|IA@&(kgSJn&A$&$TZK#2Sb1sFnGH9xvX + + + + + + +mi-malloc: Performance + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
Performance
+
+
+

We tested mimalloc against many other top allocators over a wide range of benchmarks, ranging from various real world programs to synthetic benchmarks that see how the allocator behaves under more extreme circumstances.

+

In our benchmarks, mimalloc always outperforms all other leading allocators (jemalloc, tcmalloc, Hoard, etc) (Apr 2019), and usually uses less memory (up to 25% more in the worst case). A nice property is that it does consistently well over the wide range of benchmarks.

+

See the Performance section in the mimalloc repository for benchmark results, or the the technical report for detailed benchmark results.

+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/build.html b/extlib/mimalloc/docs/build.html new file mode 100644 index 0000000..2bd06f1 --- /dev/null +++ b/extlib/mimalloc/docs/build.html @@ -0,0 +1,130 @@ + + + + + + + +mi-malloc: Building + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
Building
+
+
+

Checkout the sources from Github:

git clone https://github.com/microsoft/mimalloc

Windows

+

Open ide/vs2019/mimalloc.sln in Visual Studio 2019 and build (or ide/vs2017/mimalloc.sln). The mimalloc project builds a static library (in out/msvc-x64), while the mimalloc-override project builds a DLL for overriding malloc in the entire program.

+

macOS, Linux, BSD, etc.

+

We use cmake1 as the build system:

+
> mkdir -p out/release
> cd out/release
> cmake ../..
> make

This builds the library as a shared (dynamic) library (.so or .dylib), a static library (.a), and as a single object file (.o).

+

> sudo make install (install the library and header files in /usr/local/lib and /usr/local/include)

+

You can build the debug version which does many internal checks and maintains detailed statistics as:

+
> mkdir -p out/debug
> cd out/debug
> cmake -DCMAKE_BUILD_TYPE=Debug ../..
> make

This will name the shared library as libmimalloc-debug.so.

+

Finally, you can build a secure version that uses guard pages, encrypted free lists, etc, as:

> mkdir -p out/secure
> cd out/secure
> cmake -DMI_SECURE=ON ../..
> make

This will name the shared library as libmimalloc-secure.so. Use ccmake2 instead of cmake to see and customize all the available build options.

+

Notes:

    +
  1. Install CMake: sudo apt-get install cmake
  2. +
  3. Install CCMake: sudo apt-get install cmake-curses-gui
  4. +
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/classes.html b/extlib/mimalloc/docs/classes.html new file mode 100644 index 0000000..e74a0a2 --- /dev/null +++ b/extlib/mimalloc/docs/classes.html @@ -0,0 +1,125 @@ + + + + + + + +mi-malloc: Data Structure Index + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
Data Structure Index
+
+
+ + + + + + +
  m  
+
mi_stl_allocator   
mi_heap_area_t   
+ +
+
+ + + + diff --git a/extlib/mimalloc/docs/closed.png b/extlib/mimalloc/docs/closed.png new file mode 100644 index 0000000000000000000000000000000000000000..5e32501fa57b7c2e7c419f08fde94a485859b9e0 GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VKAtX)Ar*{o?{4I6aNu#i$ew-i zz>^CG;x8unP0+A%+o1j5B2ca2LF!#Sma`M)9^`b4e_-=XU4Qnc0|Kue=wAurTUaN| hvi;X9-ilC0^R@_)&CJRzJV0|9JYD@<);T3K0RTieEVlpv literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/doc.png b/extlib/mimalloc/docs/doc.png new file mode 100644 index 0000000000000000000000000000000000000000..5bfc88dd724c75f5585869921aae58846b235b22 GIT binary patch literal 858 zcmV-g1Eu_lP)_>9LGQRd3X1DyXWrifjzV-Y*3POEKO*ltY|3M&`L#3F}*FOLEE&gc?LBK76k*r z#gttN#CoBwHm#9nZ|YsX5OP;+(2G&FaJAaK;CB7}k07{vj&EWwd~vuRI0ydc_vQQl ze$W5DwPmwefMvrDu$(NrfD>RTUtSo^rZXAl{{7E*&dB}mw{P>;%nad31i#-$EE?s> z+FI5&tg!)D;m~y*;32*^KdP^+sU})bKtUu-Fc2W%_v7>VnVy~|UR1=Yx;oB{je`xn zy0WqlzzowZa=BbM9S&ShCw9A?e6N>iVIf+ykQxzArBXDkewr;WJkQvrZ@Kv0Wy_|R zI{4wopY$gxDsYI1-DblfB8H#{tNplozi|T~2h$d*%j5PK`+xcB_hc8d?Af`4ug5Qv zPS2yoig1Y|X%K(>Ib*D!pTFNY;Gv(T0~N61?e=z>H*F*&4i2{^(7U_YyYp2NukEF= zel;cWBB~^>OgasagQbAeB@J@e85&74JUqLGC1@pBWKQ{NxNdw zu1q=uki#RtWaZ%4TZcK?*~yW_AwC%x;K06pjE`Pm|L$Epv!?!j=+Y(tGb{yEHB|;* zUHXPo$t0ccbaNn)Aoq4q!&Knz!h$h;VR6wohZ<#2NpYNbOkJ;~@&C)b2W!&N<2?(L?f?J)07*qoM6N<$f|>}WoB#j- literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/doxygen.css b/extlib/mimalloc/docs/doxygen.css new file mode 100644 index 0000000..7238471 --- /dev/null +++ b/extlib/mimalloc/docs/doxygen.css @@ -0,0 +1,1764 @@ +/* The standard CSS for doxygen 1.8.15 */ + +body, table, div, p, dl { + font: 400 14px/22px Roboto,sans-serif; +} + +p.reference, p.definition { + font: 400 14px/22px Roboto,sans-serif; +} + +/* @group Heading Levels */ + +h1.groupheader { + font-size: 150%; +} + +.title { + font: 400 14px/28px Roboto,sans-serif; + font-size: 150%; + font-weight: bold; + margin: 10px 2px; +} + +h2.groupheader { + border-bottom: 1px solid #474D4E; + color: #0A0B0B; + font-size: 150%; + font-weight: normal; + margin-top: 1.75em; + padding-top: 8px; + padding-bottom: 4px; + width: 100%; +} + +h3.groupheader { + font-size: 100%; +} + +h1, h2, h3, h4, h5, h6 { + -webkit-transition: text-shadow 0.5s linear; + -moz-transition: text-shadow 0.5s linear; + -ms-transition: text-shadow 0.5s linear; + -o-transition: text-shadow 0.5s linear; + transition: text-shadow 0.5s linear; + margin-right: 15px; +} + +h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: 0 0 15px cyan; +} + +dt { + font-weight: bold; +} + +div.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; +} + +p.startli, p.startdd { + margin-top: 2px; +} + +p.starttd { + margin-top: 0px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +p.interli { +} + +p.interdd { +} + +p.intertd { +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +h3.version { + font-size: 90%; + text-align: center; +} + +div.qindex, div.navtab{ + background-color: #D6D9D9; + border: 1px solid #636C6D; + text-align: center; +} + +div.qindex, div.navpath { + width: 100%; + line-height: 140%; +} + +div.navtab { + margin-right: 15px; +} + +/* @group Link Styling */ + +a { + color: #0F1010; + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: #171919; +} + +a:hover { + text-decoration: underline; +} + +a.qindex { + font-weight: bold; +} + +a.qindexHL { + font-weight: bold; + background-color: #5B6364; + color: #FFFFFF; + border: 1px double #464C4D; +} + +.contents a.qindexHL:visited { + color: #FFFFFF; +} + +a.el { + font-weight: bold; +} + +a.elRef { +} + +a.code, a.code:visited, a.line, a.line:visited { + color: #171919; +} + +a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { + color: #171919; +} + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +ul { + overflow: hidden; /*Fixed: list item bullets overlap floating elements*/ +} + +#side-nav ul { + overflow: visible; /* reset ul rule for scroll bar in GENERATE_TREEVIEW window */ +} + +#main-nav ul { + overflow: visible; /* reset ul rule for the navigation bar drop down lists */ +} + +.fragment { + text-align: left; + direction: ltr; + overflow-x: auto; /*Fixed: fragment lines overlap floating elements*/ + overflow-y: hidden; +} + +pre.fragment { + border: 1px solid #90989A; + background-color: #F7F8F8; + padding: 4px 6px; + margin: 4px 8px 4px 2px; + overflow: auto; + word-wrap: break-word; + font-size: 9pt; + line-height: 125%; + font-family: monospace, fixed; + font-size: 105%; +} + +div.fragment { + padding: 0 0 1px 0; /*Fixed: last line underline overlap border*/ + margin: 4px 8px 4px 2px; + background-color: #F7F8F8; + border: 1px solid #90989A; +} + +div.line { + font-family: monospace, fixed; + font-size: 13px; + min-height: 13px; + line-height: 1.0; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + text-indent: -53px; + padding-left: 53px; + padding-bottom: 0px; + margin: 0px; + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +div.line:after { + content:"\000A"; + white-space: pre; +} + +div.line.glow { + background-color: cyan; + box-shadow: 0 0 10px cyan; +} + + +span.lineno { + padding-right: 4px; + text-align: right; + border-right: 2px solid #0F0; + background-color: #E8E8E8; + white-space: pre; +} +span.lineno a { + background-color: #D8D8D8; +} + +span.lineno a:hover { + background-color: #C8C8C8; +} + +.lineno { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.ah, span.ah { + background-color: black; + font-weight: bold; + color: #FFFFFF; + margin-bottom: 3px; + margin-top: 3px; + padding: 0.2em; + border: solid thin #333; + border-radius: 0.5em; + -webkit-border-radius: .5em; + -moz-border-radius: .5em; + box-shadow: 2px 2px 3px #999; + -webkit-box-shadow: 2px 2px 3px #999; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; + background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#000),color-stop(0.3, #444)); + background-image: -moz-linear-gradient(center top, #eee 0%, #444 40%, #000 110%); +} + +div.classindex ul { + list-style: none; + padding-left: 0; +} + +div.classindex span.ai { + display: inline-block; +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + font-weight: bold; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + background-color: white; + color: black; + margin: 0; +} + +div.contents { + margin-top: 10px; + margin-left: 12px; + margin-right: 8px; +} + +td.indexkey { + background-color: #D6D9D9; + font-weight: bold; + border: 1px solid #90989A; + margin: 2px 0px 2px 0; + padding: 2px 10px; + white-space: nowrap; + vertical-align: top; +} + +td.indexvalue { + background-color: #D6D9D9; + border: 1px solid #90989A; + padding: 2px 10px; + margin: 2px 0px; +} + +tr.memlist { + background-color: #DADDDE; +} + +p.formulaDsp { + text-align: center; +} + +img.formulaDsp { + +} + +img.formulaInl, img.inline { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { + border: 0px; +} + +address.footer { + text-align: right; + padding-right: 12px; +} + +img.footer { + border: 0px; + vertical-align: middle; +} + +/* @group Code Colorization */ + +span.keyword { + color: #008000 +} + +span.keywordtype { + color: #604020 +} + +span.keywordflow { + color: #e08000 +} + +span.comment { + color: #800000 +} + +span.preprocessor { + color: #806020 +} + +span.stringliteral { + color: #002080 +} + +span.charliteral { + color: #008080 +} + +span.vhdldigit { + color: #ff00ff +} + +span.vhdlchar { + color: #000000 +} + +span.vhdlkeyword { + color: #700070 +} + +span.vhdllogic { + color: #ff0000 +} + +blockquote { + background-color: #EDEFEF; + border-left: 2px solid #5B6364; + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; +} + +blockquote.DocNodeRTL { + border-left: 0; + border-right: 2px solid #5B6364; + margin: 0 4px 0 24px; + padding: 0 16px 0 12px; +} + +/* @end */ + +/* +.search { + color: #003399; + font-weight: bold; +} + +form.search { + margin-bottom: 0px; + margin-top: 0px; +} + +input.search { + font-size: 75%; + color: #000080; + font-weight: normal; + background-color: #e8eef2; +} +*/ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid #636C6D; +} + +th.dirtab { + background: #D6D9D9; + font-weight: bold; +} + +hr { + height: 0px; + border: none; + border-top: 1px solid #1A1D1D; +} + +hr.footer { + height: 1px; +} + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: 0px; + padding: 0px; +} + +.memberdecls td, .fieldtable tr { + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: cyan; + box-shadow: 0 0 15px cyan; +} + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background-color: #F2F3F3; + border: none; + margin: 4px; + padding: 1px 0 0 8px; +} + +.mdescLeft, .mdescRight { + padding: 0px 8px 4px 8px; + color: #555; +} + +.memSeparator { + border-bottom: 1px solid #BBC0C1; + line-height: 1px; + margin: 0px; + padding: 0px; +} + +.memItemLeft, .memTemplItemLeft { + white-space: nowrap; +} + +.memItemRight { + width: 100%; +} + +.memTemplParams { + color: #171919; + white-space: nowrap; + font-size: 80%; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtitle { + padding: 8px; + border-top: 1px solid #697273; + border-left: 1px solid #697273; + border-right: 1px solid #697273; + border-top-right-radius: 4px; + border-top-left-radius: 4px; + margin-bottom: -1px; + background-image: url('nav_f.png'); + background-repeat: repeat-x; + background-color: #C4C8C9; + line-height: 1.25; + font-weight: 300; + float:left; +} + +.permalink +{ + font-size: 65%; + display: inline-block; + vertical-align: middle; +} + +.memtemplate { + font-size: 80%; + color: #171919; + font-weight: normal; + margin-left: 9px; +} + +.memnav { + background-color: #D6D9D9; + border: 1px solid #636C6D; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 10px; + margin-right: 5px; + -webkit-transition: box-shadow 0.5s linear; + -moz-transition: box-shadow 0.5s linear; + -ms-transition: box-shadow 0.5s linear; + -o-transition: box-shadow 0.5s linear; + transition: box-shadow 0.5s linear; + display: table !important; + width: 100%; +} + +.memitem.glow { + box-shadow: 0 0 15px cyan; +} + +.memname { + font-weight: 400; + margin-left: 6px; +} + +.memname td { + vertical-align: bottom; +} + +.memproto, dl.reflist dt { + border-top: 1px solid #697273; + border-left: 1px solid #697273; + border-right: 1px solid #697273; + padding: 6px 0px 6px 0px; + color: #030303; + font-weight: bold; + text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); + background-color: #BDC2C3; + /* opera specific markup */ + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + border-top-right-radius: 4px; + /* firefox specific markup */ + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + -moz-border-radius-topright: 4px; + /* webkit specific markup */ + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + -webkit-border-top-right-radius: 4px; + +} + +.overload { + font-family: "courier new",courier,monospace; + font-size: 65%; +} + +.memdoc, dl.reflist dd { + border-bottom: 1px solid #697273; + border-left: 1px solid #697273; + border-right: 1px solid #697273; + padding: 6px 10px 2px 10px; + background-color: #F7F8F8; + border-top-width: 0; + background-image:url('nav_g.png'); + background-repeat:repeat-x; + background-color: #FFFFFF; + /* opera specific markup */ + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + /* firefox specific markup */ + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + /* webkit specific markup */ + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; +} + +.paramname { + color: #602020; + white-space: nowrap; +} +.paramname em { + font-style: normal; +} +.paramname code { + line-height: 14px; +} + +.params, .retval, .exception, .tparams { + margin-left: 0px; + padding-left: 0px; +} + +.params .paramname, .retval .paramname, .tparams .paramname { + font-weight: bold; + vertical-align: top; +} + +.params .paramtype, .tparams .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir, .tparams .paramdir { + font-family: "courier new",courier,monospace; + vertical-align: top; +} + +table.mlabels { + border-spacing: 0px; +} + +td.mlabels-left { + width: 100%; + padding: 0px; +} + +td.mlabels-right { + vertical-align: bottom; + padding: 0px; + white-space: nowrap; +} + +span.mlabels { + margin-left: 8px; +} + +span.mlabel { + background-color: #353A3B; + border-top:1px solid #212425; + border-left:1px solid #212425; + border-right:1px solid #90989A; + border-bottom:1px solid #90989A; + text-shadow: none; + color: white; + margin-right: 4px; + padding: 2px 3px; + border-radius: 3px; + font-size: 7pt; + white-space: nowrap; + vertical-align: middle; +} + + + +/* @end */ + +/* these are for tree view inside a (index) page */ + +div.directory { + margin: 10px 0px; + border-top: 1px solid #5B6364; + border-bottom: 1px solid #5B6364; + width: 100%; +} + +.directory table { + border-collapse:collapse; +} + +.directory td { + margin: 0px; + padding: 0px; + vertical-align: top; +} + +.directory td.entry { + white-space: nowrap; + padding-right: 6px; + padding-top: 3px; +} + +.directory td.entry a { + outline:none; +} + +.directory td.entry a img { + border: none; +} + +.directory td.desc { + width: 100%; + padding-left: 6px; + padding-right: 6px; + padding-top: 3px; + border-left: 1px solid rgba(0,0,0,0.05); +} + +.directory tr.even { + padding-left: 6px; + background-color: #EDEFEF; +} + +.directory img { + vertical-align: -30%; +} + +.directory .levels { + white-space: nowrap; + width: 100%; + text-align: right; + font-size: 9pt; +} + +.directory .levels span { + cursor: pointer; + padding-left: 2px; + padding-right: 2px; + color: #0F1010; +} + +.arrow { + color: #5B6364; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + font-size: 80%; + display: inline-block; + width: 16px; + height: 22px; +} + +.icon { + font-family: Arial, Helvetica; + font-weight: bold; + font-size: 12px; + height: 14px; + width: 16px; + display: inline-block; + background-color: #353A3B; + color: white; + text-align: center; + border-radius: 4px; + margin-left: 2px; + margin-right: 2px; +} + +.icona { + width: 24px; + height: 22px; + display: inline-block; +} + +.iconfopen { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:url('folderopen.png'); + background-position: 0px -4px; + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +.iconfclosed { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:url('folderclosed.png'); + background-position: 0px -4px; + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +.icondoc { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:url('doc.png'); + background-position: 0px -4px; + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +table.directory { + font: 400 14px Roboto,sans-serif; +} + +/* @end */ + +div.dynheader { + margin-top: 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +address { + font-style: normal; + color: #050505; +} + +table.doxtable caption { + caption-side: top; +} + +table.doxtable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.doxtable td, table.doxtable th { + border: 1px solid #060606; + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: #0B0C0C; + color: #FFFFFF; + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +table.fieldtable { + /*width: 100%;*/ + margin-bottom: 10px; + border: 1px solid #697273; + border-spacing: 0px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; + -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); +} + +.fieldtable td, .fieldtable th { + padding: 3px 7px 2px; +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + white-space: nowrap; + border-right: 1px solid #697273; + border-bottom: 1px solid #697273; + vertical-align: top; +} + +.fieldtable td.fieldname { + padding-top: 3px; +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid #697273; + /*width: 100%;*/ +} + +.fieldtable td.fielddoc p:first-child { + margin-top: 0px; +} + +.fieldtable td.fielddoc p:last-child { + margin-bottom: 2px; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable th { + background-image:url('nav_f.png'); + background-repeat:repeat-x; + background-color: #C4C8C9; + font-size: 90%; + color: #030303; + padding-bottom: 4px; + padding-top: 5px; + text-align:left; + font-weight: 400; + -moz-border-radius-topleft: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom: 1px solid #697273; +} + + +.tabsearch { + top: 0px; + left: 10px; + height: 36px; + background-image: url('tab_b.png'); + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +.navpath ul +{ + font-size: 11px; + background-image:url('tab_b.png'); + background-repeat:repeat-x; + background-position: 0 -5px; + height:30px; + line-height:30px; + color:#494F50; + border:solid 1px #8C9596; + overflow:hidden; + margin:0px; + padding:0px; +} + +.navpath li +{ + list-style-type:none; + float:left; + padding-left:10px; + padding-right:15px; + background-image:url('bc_s.png'); + background-repeat:no-repeat; + background-position:right; + color:#0A0B0B; +} + +.navpath li.navelem a +{ + height:32px; + display:block; + text-decoration: none; + outline: none; + color: #040404; + font-family: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; + text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); + text-decoration: none; +} + +.navpath li.navelem a:hover +{ + color:#2E3233; +} + +.navpath li.footer +{ + list-style-type:none; + float:right; + padding-left:10px; + padding-right:15px; + background-image:none; + background-repeat:no-repeat; + background-position:right; + color:#0A0B0B; + font-size: 8pt; +} + + +div.summary +{ + float: right; + font-size: 8pt; + padding-right: 5px; + width: 50%; + text-align: right; +} + +div.summary a +{ + white-space: nowrap; +} + +table.classindex +{ + margin: 10px; + white-space: nowrap; + margin-left: 3%; + margin-right: 3%; + width: 94%; + border: 0; + border-spacing: 0; + padding: 0; +} + +div.ingroups +{ + font-size: 8pt; + width: 50%; + text-align: left; +} + +div.ingroups a +{ + white-space: nowrap; +} + +div.header +{ + background-image:url('nav_h.png'); + background-repeat:repeat-x; + background-color: #F2F3F3; + margin: 0px; + border-bottom: 1px solid #90989A; +} + +div.headertitle +{ + padding: 5px 5px 5px 10px; +} + +.PageDocRTL-title div.headertitle { + text-align: right; + direction: rtl; +} + +dl { + padding: 0 0 0 0; +} + +/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug, dl.examples */ +dl.section { + margin-left: 0px; + padding-left: 0px; +} + +dl.section.DocNodeRTL { + margin-right: 0px; + padding-right: 0px; +} + +dl.note { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #D0C000; +} + +dl.note.DocNodeRTL { + margin-left: 0; + padding-left: 0; + border-left: 0; + margin-right: -7px; + padding-right: 3px; + border-right: 4px solid; + border-color: #D0C000; +} + +dl.warning, dl.attention { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #FF0000; +} + +dl.warning.DocNodeRTL, dl.attention.DocNodeRTL { + margin-left: 0; + padding-left: 0; + border-left: 0; + margin-right: -7px; + padding-right: 3px; + border-right: 4px solid; + border-color: #FF0000; +} + +dl.pre, dl.post, dl.invariant { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #00D000; +} + +dl.pre.DocNodeRTL, dl.post.DocNodeRTL, dl.invariant.DocNodeRTL { + margin-left: 0; + padding-left: 0; + border-left: 0; + margin-right: -7px; + padding-right: 3px; + border-right: 4px solid; + border-color: #00D000; +} + +dl.deprecated { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #505050; +} + +dl.deprecated.DocNodeRTL { + margin-left: 0; + padding-left: 0; + border-left: 0; + margin-right: -7px; + padding-right: 3px; + border-right: 4px solid; + border-color: #505050; +} + +dl.todo { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #00C0E0; +} + +dl.todo.DocNodeRTL { + margin-left: 0; + padding-left: 0; + border-left: 0; + margin-right: -7px; + padding-right: 3px; + border-right: 4px solid; + border-color: #00C0E0; +} + +dl.test { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #3030E0; +} + +dl.test.DocNodeRTL { + margin-left: 0; + padding-left: 0; + border-left: 0; + margin-right: -7px; + padding-right: 3px; + border-right: 4px solid; + border-color: #3030E0; +} + +dl.bug { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #C08050; +} + +dl.bug.DocNodeRTL { + margin-left: 0; + padding-left: 0; + border-left: 0; + margin-right: -7px; + padding-right: 3px; + border-right: 4px solid; + border-color: #C08050; +} + +dl.section dd { + margin-bottom: 6px; +} + + +#projectlogo +{ + text-align: center; + vertical-align: bottom; + border-collapse: separate; +} + +#projectlogo img +{ + border: 0px none; +} + +#projectalign +{ + vertical-align: middle; +} + +#projectname +{ + font: 300% Tahoma, Arial,sans-serif; + margin: 0px; + padding: 2px 0px; +} + +#projectbrief +{ + font: 120% Tahoma, Arial,sans-serif; + margin: 0px; + padding: 0px; +} + +#projectnumber +{ + font: 50% Tahoma, Arial,sans-serif; + margin: 0px; + padding: 0px; +} + +#titlearea +{ + padding: 0px; + margin: 0px; + width: 100%; + border-bottom: 1px solid #212425; +} + +.image +{ + text-align: center; +} + +.dotgraph +{ + text-align: center; +} + +.mscgraph +{ + text-align: center; +} + +.plantumlgraph +{ + text-align: center; +} + +.diagraph +{ + text-align: center; +} + +.caption +{ + font-weight: bold; +} + +div.zoom +{ + border: 1px solid #4F5657; +} + +dl.citelist { + margin-bottom:50px; +} + +dl.citelist dt { + color:#080909; + float:left; + font-weight:bold; + margin-right:10px; + padding:5px; +} + +dl.citelist dd { + margin:2px 0; + padding:5px 0; +} + +div.toc { + padding: 14px 25px; + background-color: #E8EAEA; + border: 1px solid #B1B7B8; + border-radius: 7px 7px 7px 7px; + float: right; + height: auto; + margin: 0 8px 10px 10px; + width: 200px; +} + +.PageDocRTL-title div.toc { + float: left !important; + text-align: right; +} + +div.toc li { + background: url("bdwn.png") no-repeat scroll 0 5px transparent; + font: 10px/1.2 Verdana,DejaVu Sans,Geneva,sans-serif; + margin-top: 5px; + padding-left: 10px; + padding-top: 2px; +} + +.PageDocRTL-title div.toc li { + background-position-x: right !important; + padding-left: 0 !important; + padding-right: 10px; +} + +div.toc h3 { + font: bold 12px/1.2 Arial,FreeSans,sans-serif; + color: #171919; + border-bottom: 0 none; + margin: 0; +} + +div.toc ul { + list-style: none outside none; + border: medium none; + padding: 0px; +} + +div.toc li.level1 { + margin-left: 0px; +} + +div.toc li.level2 { + margin-left: 15px; +} + +div.toc li.level3 { + margin-left: 30px; +} + +div.toc li.level4 { + margin-left: 45px; +} + +.PageDocRTL-title div.toc li.level1 { + margin-left: 0 !important; + margin-right: 0; +} + +.PageDocRTL-title div.toc li.level2 { + margin-left: 0 !important; + margin-right: 15px; +} + +.PageDocRTL-title div.toc li.level3 { + margin-left: 0 !important; + margin-right: 30px; +} + +.PageDocRTL-title div.toc li.level4 { + margin-left: 0 !important; + margin-right: 45px; +} + +.inherit_header { + font-weight: bold; + color: gray; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.inherit_header td { + padding: 6px 0px 2px 5px; +} + +.inherit { + display: none; +} + +tr.heading h2 { + margin-top: 12px; + margin-bottom: 4px; +} + +/* tooltip related style info */ + +.ttc { + position: absolute; + display: none; +} + +#powerTip { + cursor: default; + white-space: nowrap; + background-color: white; + border: 1px solid gray; + border-radius: 4px 4px 4px 4px; + box-shadow: 1px 1px 7px gray; + display: none; + font-size: smaller; + max-width: 80%; + opacity: 0.9; + padding: 1ex 1em 1em; + position: absolute; + z-index: 2147483647; +} + +#powerTip div.ttdoc { + color: grey; + font-style: italic; +} + +#powerTip div.ttname a { + font-weight: bold; +} + +#powerTip div.ttname { + font-weight: bold; +} + +#powerTip div.ttdeci { + color: #006318; +} + +#powerTip div { + margin: 0px; + padding: 0px; + font: 12px/16px Roboto,sans-serif; +} + +#powerTip:before, #powerTip:after { + content: ""; + position: absolute; + margin: 0px; +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.s:after, #powerTip.s:before, +#powerTip.w:after, #powerTip.w:before, +#powerTip.e:after, #powerTip.e:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.nw:after, #powerTip.nw:before, +#powerTip.sw:after, #powerTip.sw:before { + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; +} + +#powerTip.n:after, #powerTip.s:after, +#powerTip.w:after, #powerTip.e:after, +#powerTip.nw:after, #powerTip.ne:after, +#powerTip.sw:after, #powerTip.se:after { + border-color: rgba(255, 255, 255, 0); +} + +#powerTip.n:before, #powerTip.s:before, +#powerTip.w:before, #powerTip.e:before, +#powerTip.nw:before, #powerTip.ne:before, +#powerTip.sw:before, #powerTip.se:before { + border-color: rgba(128, 128, 128, 0); +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.nw:after, #powerTip.nw:before { + top: 100%; +} + +#powerTip.n:after, #powerTip.ne:after, #powerTip.nw:after { + border-top-color: #FFFFFF; + border-width: 10px; + margin: 0px -10px; +} +#powerTip.n:before { + border-top-color: #808080; + border-width: 11px; + margin: 0px -11px; +} +#powerTip.n:after, #powerTip.n:before { + left: 50%; +} + +#powerTip.nw:after, #powerTip.nw:before { + right: 14px; +} + +#powerTip.ne:after, #powerTip.ne:before { + left: 14px; +} + +#powerTip.s:after, #powerTip.s:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.sw:after, #powerTip.sw:before { + bottom: 100%; +} + +#powerTip.s:after, #powerTip.se:after, #powerTip.sw:after { + border-bottom-color: #FFFFFF; + border-width: 10px; + margin: 0px -10px; +} + +#powerTip.s:before, #powerTip.se:before, #powerTip.sw:before { + border-bottom-color: #808080; + border-width: 11px; + margin: 0px -11px; +} + +#powerTip.s:after, #powerTip.s:before { + left: 50%; +} + +#powerTip.sw:after, #powerTip.sw:before { + right: 14px; +} + +#powerTip.se:after, #powerTip.se:before { + left: 14px; +} + +#powerTip.e:after, #powerTip.e:before { + left: 100%; +} +#powerTip.e:after { + border-left-color: #FFFFFF; + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.e:before { + border-left-color: #808080; + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +#powerTip.w:after, #powerTip.w:before { + right: 100%; +} +#powerTip.w:after { + border-right-color: #FFFFFF; + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.w:before { + border-right-color: #808080; + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +@media print +{ + #top { display: none; } + #side-nav { display: none; } + #nav-path { display: none; } + body { overflow:visible; } + h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } + .summary { display: none; } + .memitem { page-break-inside: avoid; } + #doc-content + { + margin-left:0 !important; + height:auto !important; + width:auto !important; + overflow:inherit; + display:inline; + } +} + +/* @group Markdown */ + +/* +table.markdownTable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.markdownTable td, table.markdownTable th { + border: 1px solid #060606; + padding: 3px 7px 2px; +} + +table.markdownTableHead tr { +} + +table.markdownTableBodyLeft td, table.markdownTable th { + border: 1px solid #060606; + padding: 3px 7px 2px; +} + +th.markdownTableHeadLeft th.markdownTableHeadRight th.markdownTableHeadCenter th.markdownTableHeadNone { + background-color: #0B0C0C; + color: #FFFFFF; + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +th.markdownTableHeadLeft { + text-align: left +} + +th.markdownTableHeadRight { + text-align: right +} + +th.markdownTableHeadCenter { + text-align: center +} +*/ + +table.markdownTable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.markdownTable td, table.markdownTable th { + border: 1px solid #060606; + padding: 3px 7px 2px; +} + +table.markdownTable tr { +} + +th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { + background-color: #0B0C0C; + color: #FFFFFF; + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +th.markdownTableHeadLeft, td.markdownTableBodyLeft { + text-align: left +} + +th.markdownTableHeadRight, td.markdownTableBodyRight { + text-align: right +} + +th.markdownTableHeadCenter, td.markdownTableBodyCenter { + text-align: center +} + +.DocNodeRTL { + text-align: right; + direction: rtl; +} + +.DocNodeLTR { + text-align: left; + direction: ltr; +} + +table.DocNodeRTL { + width: auto; + margin-right: 0; + margin-left: auto; +} + +table.DocNodeLTR { + width: auto; + margin-right: auto; + margin-left: 0; +} + +tt, code, kbd, samp +{ + display: inline-block; + direction:ltr; +} +/* @end */ + +u { + text-decoration: underline; +} + diff --git a/extlib/mimalloc/docs/doxygen.png b/extlib/mimalloc/docs/doxygen.png new file mode 100644 index 0000000000000000000000000000000000000000..c0249e9e9a20cc6cea02889ee04dcca8c773d36a GIT binary patch literal 3777 zcmV;y4nFaTP)P-5Hxsw9`XeR*pWpuXysjI810GPI&ogD)f0|4y=0RJ7jTY%8i)C2%lWa}IN z+6e%Do*Wz;0Kg`-0|Ws7L;58EITERe6vpoo0f^>i0I(vf*qYuti zTzOrbp3pH;Gpd`DF9pik44a2y=iNe9buf9|q_DKMf+5iw)va%yhu+1iS)x+-_6~__mRPaF>=&sNq&!hhvG~{1?w=_oa=t0j#W$z z^$jeS$@z^@{6VM}$FaAdKt-Ts5=g>)mjg7n>jMwO?b@>!{XN{J!#R?&Dj|9w5M=P-8kCG-5`1O1H`eMiS?Gpd> zq$SACT&E)6fd1k&aXw1l?C2ye>KVf4th3k<+) zKk~592|5!6ce?Mstp$a9(BIvZ8~|9->2v2WAz&OD8ycqaH~HLJSoE2h-k7Jxpz{3r zWU?7&Y4h>N{ZLy|ll;4ZgW`7Y+9jQTwEa^oo)=S6{(Xf%#ifr~xL7eq9VzkuIDR}% zm6gt>zuu#sen!1z(X?)9Q3NlF-@n7qDo-0-Qf{27UYWgPh09 z+sjJ?NI&bfSjF^+S0r9uegW34N)`Ukm^%;3pa~Lh{igS@cE$3d;|4<@*+}Z+>K0eH zcaNE0Wl$g%Yu_=`n?557)M_*7ZvtPYqu+f8z_k5_4n<>QqZjcEmb{@uhM?uvEw4_U zIvI`}Jqp0Imo8s{UB=fI5)lD;9-*Nx;k%M=<0;k@inTZ~d4;b$rm>^Pp!RYN&x=|D zPw`>i-{H~MP2xW}Vg#;VyXMaw)~QEN{r)e%0ASjj>(0VLJQomPrYDQpMFhyOX5%K$ zC;Ut^Z1SW@s3NN;2|xNTs=&1}f!x9{AIC5sktA*PbfS<=x>37e*jn?FyInKmsP|2lRw zzK^S`Hou@iI!|7iijT6l@J#sjF`uE@En7BAyp3D3u`YEb4|Hx}zLtE^CJ%42tFvX- zc;p!nG*R`Ac1c_MC5}CM==T<}tJ~}ABi|x=dL-AWh-#!b^vyQ_O#4}d0(F-!2QolH zd;6mJfW*8|S&1nx1lQ4Du}U|;I%ooc*Qk*rgiGe^;-bs@#MJLo)6=m{my<{Gp+-5~ zq|ld?^P;0A8RZMXsH&_CC7C?pdyxOqxP&B3{9^zBm=VVk9vXrh*RMyByih&TQQGqR z>~j;8Nr{Q3R}d0{)2GV0y(ui?5P4)bi)?DVynVDAHf)gik&%%w96!bdb{a8iv?g`s zN{P2wr^8qO_y+*f9`*Fo6swT7B(otsO}GG#PCfK`Sz8u))W!m7-V`rpSvc{} z@8U^b7_20xxeJ-_E?v83d|I#*0L;iJn@q9^7XB-y@ZkiqNTlG0hmdC_mNK29oWfpNo^hwbTWGJCUr8DYj~C1W0cyHhbZ#6{v4$ zkeJQQO#omV>_}fzcPBQt0do~9JjGMeACr9xY8qDJQ;m03Qdcweev)b_g zan{C-s@T|AQ{VRU!()R7Np_bnU(VoUk^MTdFXUxrhw_%kVJl9tCbnt-0Bw<39q&+M zTd@f*sGFmseshja;%&>zL+++5vloRW{W@8ZKKXPz56>c!7Z4;l-T3E& zm0@AiB$*jhxGO)^1(Hk=@q@|#*5tI+aPHk30IVo_<_uiAcrlse&DtF4R;FgGcWg_q zs=6BUqNdl8jxa|_Rx@DmquM0x+g7m}3pvbZxSw*tqQw$FA^vro{Pzh42$s6X4F)WG zGrofUMv;&GvX~sXzEt$39qsMW)Y#}n-=4PWBmy)gY?|VQ=Yy?k!~pTPAvPh&liP!I zLlP2js;snx|99)&-8X&RdH|*!{^C^raUtWCOIM{+aeGO;C^CNY#tq?hbnenclVR~P zB53GSU|cxvAs^82Rs;nFN&Ni$e8|76sxM*D+-TOBy7_~xQX{7rwBEypYv#+^oVeC! zWZ=N(`?E-Xwoz6=dFU6`s-)8$A2S=JzW#&iPcCkdc6BS()>snlu!rd?cI zEd2WW`vZU(sl#3t>QAKo{ZL;`^!)h}?;;g+>-)Epnfh`V9+H8VzH%l0d9)-mGyDA* zvXS=ifPw0TZ@gjVGdpuV9`_#wc|5xcb|PQvuES#6%#<-4h{|n7rv!M?_NCtz>KOd)mWWJN4_co7PgQw;=gv!$(^ms9?YQVUsK0qLifm@g)Wl-ozyT63 z_1$HtC@;@U->^}dM-+!)cI*OAxg5v4WYK~;k`vmSOg5Z+X2iak*k+y_pF9Vnzj(5f zLb@Itp5K%Hs<4O%0JtNG`nLh}%?ztJhBSs=5EDQjw zD3{tp$)SS`4l91btx+8_7Xt>E3cETb1*cDyXOhhrvN@LllqV$yP5}CojL?2bPCwyK zO2r?>jWhH0&DRIANJeOH7I7z$?>G<7hvDVzEu2-HJc;tsf5XkAALeK@mhr@s9id&5 zE-R?1*ecF7ST@w<>j<^XqeN`8si{erK@3phvUknuTJZNg2_VZH5*IG80!RSK-oYWx z&D~x2pdN_Yni_5^vf?&l}TzWY`Z=O0RTq&8`^!Fp_my8!I%9V8z4745O_EYhv8kqh%P z383u+P_U6F#v|lOyckP>cyaLJ)nMah!2vMa+LTlPrX8=D$NKEZ(RftH!H}E~C;5UwkUze;08E?YRn9IhYaFRVTsV7{ig{HyCy?RVw=d*& zc)b~`;TP{hwm1g>up+YQOFvGsdRScw0I)@i=Skff0IWy=h!E7#{}2H94bdDb-fI*8 zJgMJfK0~F$ZMqzsDl6s0_(qN;3Ke-Rt*xuv`}La@8u6;>$^$?fB(uXWWjxVj0QeOt zx4F4F)L#7R^^h~@b^*XWPc#?!_V=YW>h|amZ@OTnE^z+r8KJ#4HZ%wis#|wQxcBdm zlP6A~zM;MgS_!C@7HUf!0iaFVw`&&-_mjc}Hy-tLp%>PpXDnf85dipqK + + + + + + +mi-malloc: Environment Options + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
Environment Options
+
+
+

You can set further options either programmatically (using mi_option_set), or via environment variables.

+
    +
  • MIMALLOC_SHOW_STATS=1: show statistics when the program terminates.
  • +
  • MIMALLOC_VERBOSE=1: show verbose messages.
  • +
  • MIMALLOC_SHOW_ERRORS=1: show error and warning messages.
  • +
  • MIMALLOC_PAGE_RESET=0: by default, mimalloc will reset (or purge) OS pages when not in use to signal to the OS that the underlying physical memory can be reused. This can reduce memory fragmentation in long running (server) programs. By setting it to 0 no such page resets will be done which can improve performance for programs that are not long running. As an alternative, the MIMALLOC_RESET_DELAY=<msecs> can be set higher (100ms by default) to make the page reset occur less frequently instead of turning it off completely.
  • +
  • MIMALLOC_LARGE_OS_PAGES=1: use large OS pages (2MiB) when available; for some workloads this can significantly improve performance. Use MIMALLOC_VERBOSE to check if the large OS pages are enabled – usually one needs to explicitly allow large OS pages (as on Windows and Linux). However, sometimes the OS is very slow to reserve contiguous physical memory for large OS pages so use with care on systems that can have fragmented memory (for that reason, we generally recommend to use MIMALLOC_RESERVE_HUGE_OS_PAGES instead when possible).
  • +
  • MIMALLOC_RESERVE_HUGE_OS_PAGES=N: where N is the number of 1GiB huge OS pages. This reserves the huge pages at startup and sometimes this can give a large (latency) performance improvement on big workloads. Usually it is better to not use MIMALLOC_LARGE_OS_PAGES in combination with this setting. Just like large OS pages, use with care as reserving contiguous physical memory can take a long time when memory is fragmented (but reserving the huge pages is done at startup only once). Note that we usually need to explicitly enable huge OS pages (as on Windows and Linux)). With huge OS pages, it may be beneficial to set the setting MIMALLOC_EAGER_COMMIT_DELAY=N (N is 1 by default) to delay the initial N segments (of 4MiB) of a thread to not allocate in the huge OS pages; this prevents threads that are short lived and allocate just a little to take up space in the huge OS page area (which cannot be reset).
  • +
+

Use caution when using fork in combination with either large or huge OS pages: on a fork, the OS uses copy-on-write for all pages in the original process including the huge OS pages. When any memory is now written in that area, the OS will copy the entire 1GiB huge page (or 2MiB large page) which can cause the memory usage to grow in big increments.

+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/folderclosed.png b/extlib/mimalloc/docs/folderclosed.png new file mode 100644 index 0000000000000000000000000000000000000000..53b7aa6ae298edbe9de5ec8b3d61a238c2150866 GIT binary patch literal 617 zcmV-v0+#)WP)xPT7;^LX)c zpgs?W0J8h`ogSWTZ#e*P9#jf2c`ImVHeCl_KJ5eBTL5q_IP*<@wE0+l|MnHvTdnj9 zjtiv{9F9im+@*Pv=rMos+NG5hynpw$;L_{&`v9{0_3mzeXXh!dt*+u~qk+bi%ldQg zZV#Q#Ep)qG8arrG0XNsyaHH8Iv=Fod4EOf1xv}y5)QEok{DmY*oh4MgOD=c}!_YdI z9!L7QloC-KL^G`*I zgveM#S1qI_HNyNH0IE=G^1b^H)Wh|48-ZzHDl9FTAd&oD&-1YV@dKmKH5S-fND2for5s^?7k~gyhrRT<_=XwCLn~BckfUhu|K*qvW4FL1t zrm`D5uT@f2_u%m0Aa~BF>U?E07k14&r}5(QG5{1%5_mNjyfHByQ%q45>%OFr`W|>n{Qv)G3m5ncf`~hH?1Xy>00000NkvXXu0mjf DES(-r literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/folderopen.png b/extlib/mimalloc/docs/folderopen.png new file mode 100644 index 0000000000000000000000000000000000000000..92225ee954bb39ac4ad7c736bb91ae06bd7b9a69 GIT binary patch literal 687 zcmV;g0#N;lP)PPDeQBA?IW+}WWq0H}tkMVDfi>CpKL^z)~Wc#_RxdTI*kbQ+070?A|&)0309 zH#39GgZr3$l)+pshc8P@IC1(69UhIDT4<^unM%b6qEWazI^gN_ZrDyZ-5v%vTpn&+ zuotE=RRyJ*uo?$j=T_BJNRq^S zjqRIfz$CzfiiIK?Eafd(k_D+aSSC>VS%StaClta=K#!{>AXP6OJ$Z`y{JSA5vLNSV zWpXMeM<=B0;B^r`O+1PG>MGv9dRZ||rV2RG`uY!8*aY#c1i?lNj2TzNs9d~ui%b<9 zK6ae)!&Kz*fEo9-s&DnXpttDz+8RE*dt1K(wNyfhS)RGRru(M%a$%-q>fU4{;qS!(kE(g-B0mAMpo! zNOvGWcKQ9n-`m?uLVbPYK%}3ri%AZ#m~bRQjzpt#n^rJ-arg=V{4M + + + + + + +mi-malloc: Data Fields + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
Here is a list of all struct and union fields with links to the structures/unions they belong to:
+
+
+ + + + diff --git a/extlib/mimalloc/docs/functions_vars.html b/extlib/mimalloc/docs/functions_vars.html new file mode 100644 index 0000000..7d41d10 --- /dev/null +++ b/extlib/mimalloc/docs/functions_vars.html @@ -0,0 +1,129 @@ + + + + + + + +mi-malloc: Data Fields - Variables + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
+ + + + diff --git a/extlib/mimalloc/docs/group__aligned.html b/extlib/mimalloc/docs/group__aligned.html new file mode 100644 index 0000000..a3eaacf --- /dev/null +++ b/extlib/mimalloc/docs/group__aligned.html @@ -0,0 +1,447 @@ + + + + + + + +mi-malloc: Aligned Allocation + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
Aligned Allocation
+
+
+ +

Allocating aligned memory blocks. +More...

+ + + + + + + + + + + + + + + + + + + + +

+Functions

void * mi_malloc_aligned (size_t size, size_t alignment)
 Allocate size bytes aligned by alignment. More...
 
void * mi_zalloc_aligned (size_t size, size_t alignment)
 
void * mi_calloc_aligned (size_t count, size_t size, size_t alignment)
 
void * mi_realloc_aligned (void *p, size_t newsize, size_t alignment)
 
void * mi_malloc_aligned_at (size_t size, size_t alignment, size_t offset)
 Allocate size bytes aligned by alignment at a specified offset. More...
 
void * mi_zalloc_aligned_at (size_t size, size_t alignment, size_t offset)
 
void * mi_calloc_aligned_at (size_t count, size_t size, size_t alignment, size_t offset)
 
void * mi_realloc_aligned_at (void *p, size_t newsize, size_t alignment, size_t offset)
 
+

Detailed Description

+

Allocating aligned memory blocks.

+

Function Documentation

+ +

◆ mi_calloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_calloc_aligned (size_t count,
size_t size,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_calloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_calloc_aligned_at (size_t count,
size_t size,
size_t alignment,
size_t offset 
)
+
+ +
+
+ +

◆ mi_malloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_malloc_aligned (size_t size,
size_t alignment 
)
+
+ +

Allocate size bytes aligned by alignment.

+
Parameters
+ + + +
sizenumber of bytes to allocate.
alignmentthe minimal alignment of the allocated memory.
+
+
+
Returns
pointer to the allocated memory or NULL if out of memory. The returned pointer is aligned by alignment, i.e. (uintptr_t)p % alignment == 0.
+

Returns a unique pointer if called with size 0.

See also
_aligned_malloc (on Windows)
+
+aligned_alloc (on BSD, with switched arguments!)
+
+posix_memalign (on Posix, with switched arguments!)
+
+memalign (on Linux, with switched arguments!)
+ +
+
+ +

◆ mi_malloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_malloc_aligned_at (size_t size,
size_t alignment,
size_t offset 
)
+
+ +

Allocate size bytes aligned by alignment at a specified offset.

+
Parameters
+ + + + +
sizenumber of bytes to allocate.
alignmentthe minimal alignment of the allocated memory at offset.
offsetthe offset that should be aligned.
+
+
+
Returns
pointer to the allocated memory or NULL if out of memory. The returned pointer is aligned by alignment at offset, i.e. ((uintptr_t)p + offset) % alignment == 0.
+

Returns a unique pointer if called with size 0.

See also
_aligned_offset_malloc (on Windows)
+ +
+
+ +

◆ mi_realloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_realloc_aligned (void * p,
size_t newsize,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_realloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_realloc_aligned_at (void * p,
size_t newsize,
size_t alignment,
size_t offset 
)
+
+ +
+
+ +

◆ mi_zalloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_zalloc_aligned (size_t size,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_zalloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_zalloc_aligned_at (size_t size,
size_t alignment,
size_t offset 
)
+
+ +
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/group__aligned.js b/extlib/mimalloc/docs/group__aligned.js new file mode 100644 index 0000000..0a5aa5c --- /dev/null +++ b/extlib/mimalloc/docs/group__aligned.js @@ -0,0 +1,11 @@ +var group__aligned = +[ + [ "mi_calloc_aligned", "group__aligned.html#ga53dddb4724042a90315b94bc268fb4c9", null ], + [ "mi_calloc_aligned_at", "group__aligned.html#ga08647c4593f3b2eef24a919a73eba3a3", null ], + [ "mi_malloc_aligned", "group__aligned.html#ga68930196751fa2cca9e1fd0d71bade56", null ], + [ "mi_malloc_aligned_at", "group__aligned.html#ga5850da130c936bd77db039dcfbc8295d", null ], + [ "mi_realloc_aligned", "group__aligned.html#ga4028d1cf4aa4c87c880747044a8322ae", null ], + [ "mi_realloc_aligned_at", "group__aligned.html#gaf66a9ae6c6f08bd6be6fb6ea771faffb", null ], + [ "mi_zalloc_aligned", "group__aligned.html#ga0cadbcf5b89a7b6fb171bc8df8734819", null ], + [ "mi_zalloc_aligned_at", "group__aligned.html#ga5f8c2353766db522565e642fafd8a3f8", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/group__analysis.html b/extlib/mimalloc/docs/group__analysis.html new file mode 100644 index 0000000..2487c24 --- /dev/null +++ b/extlib/mimalloc/docs/group__analysis.html @@ -0,0 +1,385 @@ + + + + + + + +mi-malloc: Heap Introspection + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
Heap Introspection
+
+
+ +

Inspect the heap at runtime. +More...

+ + + + + +

+Data Structures

struct  mi_heap_area_t
 An area of heap space contains blocks of a single size. More...
 
+ + + + +

+Typedefs

typedef bool() mi_block_visit_fun(const mi_heap_t *heap, const mi_heap_area_t *area, void *block, size_t block_size, void *arg)
 Visitor function passed to mi_heap_visit_blocks() More...
 
+ + + + + + + + + + + + + +

+Functions

bool mi_heap_contains_block (mi_heap_t *heap, const void *p)
 Does a heap contain a pointer to a previously allocated block? More...
 
bool mi_heap_check_owned (mi_heap_t *heap, const void *p)
 Check safely if any pointer is part of a heap. More...
 
bool mi_check_owned (const void *p)
 Check safely if any pointer is part of the default heap of this thread. More...
 
bool mi_heap_visit_blocks (const mi_heap_t *heap, bool visit_all_blocks, mi_block_visit_fun *visitor, void *arg)
 Visit all areas and blocks in a heap. More...
 
+

Detailed Description

+

Inspect the heap at runtime.

+

Data Structure Documentation

+ +

◆ mi_heap_area_t

+ +
+
+ + + + +
struct mi_heap_area_t
+
+

An area of heap space contains blocks of a single size.

+

The bytes in freed blocks are committed - used.

+
+ + + + + + + + + + + + + + + + +
Data Fields
+size_t +block_size +size in bytes of one block
+void * +blocks +start of the area containing heap blocks
+size_t +committed +current committed bytes of this area
+size_t +reserved +bytes reserved for this area
+size_t +used +bytes in use by allocated blocks
+ +
+
+

Typedef Documentation

+ +

◆ mi_block_visit_fun

+ +
+
+ + + + +
typedef bool() mi_block_visit_fun(const mi_heap_t *heap, const mi_heap_area_t *area, void *block, size_t block_size, void *arg)
+
+ +

Visitor function passed to mi_heap_visit_blocks()

+
Returns
true if ok, false to stop visiting (i.e. break)
+

This function is always first called for every area with block as a NULL pointer. If visit_all_blocks was true, the function is then called for every allocated block in that area.

+ +
+
+

Function Documentation

+ +

◆ mi_check_owned()

+ +
+
+ + + + + + + + +
bool mi_check_owned (const void * p)
+
+ +

Check safely if any pointer is part of the default heap of this thread.

+
Parameters
+ + +
pAny pointer – not required to be previously allocated by us.
+
+
+
Returns
true if p points to a block in default heap of this thread.
+

Note: expensive function, linear in the pages in the heap.

See also
mi_heap_contains_block()
+
+mi_heap_get_default()
+ +
+
+ +

◆ mi_heap_check_owned()

+ +
+
+ + + + + + + + + + + + + + + + + + +
bool mi_heap_check_owned (mi_heap_theap,
const void * p 
)
+
+ +

Check safely if any pointer is part of a heap.

+
Parameters
+ + + +
heapThe heap.
pAny pointer – not required to be previously allocated by us.
+
+
+
Returns
true if p points to a block in heap.
+

Note: expensive function, linear in the pages in the heap.

See also
mi_heap_contains_block()
+
+mi_heap_get_default()
+ +
+
+ +

◆ mi_heap_contains_block()

+ +
+
+ + + + + + + + + + + + + + + + + + +
bool mi_heap_contains_block (mi_heap_theap,
const void * p 
)
+
+ +

Does a heap contain a pointer to a previously allocated block?

+
Parameters
+ + + +
heapThe heap.
pPointer to a previously allocated block (in any heap)– cannot be some random pointer!
+
+
+
Returns
true if the block pointed to by p is in the heap.
+
See also
mi_heap_check_owned()
+ +
+
+ +

◆ mi_heap_visit_blocks()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
bool mi_heap_visit_blocks (const mi_heap_theap,
bool visit_all_blocks,
mi_block_visit_funvisitor,
void * arg 
)
+
+ +

Visit all areas and blocks in a heap.

+
Parameters
+ + + + + +
heapThe heap to visit.
visit_all_blocksIf true visits all allocated blocks, otherwise visitor is only called for every heap area.
visitorThis function is called for every area in the heap (with block as NULL). If visit_all_blocks is true, visitor is also called for every allocated block in every area (with block!=NULL). return false from this function to stop visiting early.
argExtra argument passed to visitor.
+
+
+
Returns
true if all areas and blocks were visited.
+ +
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/group__analysis.js b/extlib/mimalloc/docs/group__analysis.js new file mode 100644 index 0000000..3517836 --- /dev/null +++ b/extlib/mimalloc/docs/group__analysis.js @@ -0,0 +1,15 @@ +var group__analysis = +[ + [ "mi_heap_area_t", "group__analysis.html#structmi__heap__area__t", [ + [ "block_size", "group__analysis.html#a332a6c14d736a99699d5453a1cb04b41", null ], + [ "blocks", "group__analysis.html#ae0085e6e1cf059a4eb7767e30e9991b8", null ], + [ "committed", "group__analysis.html#ab47526df656d8837ec3e97f11b83f835", null ], + [ "reserved", "group__analysis.html#ae848a3e6840414891035423948ca0383", null ], + [ "used", "group__analysis.html#ab820302c5cd0df133eb8e51650a008b4", null ] + ] ], + [ "mi_block_visit_fun", "group__analysis.html#gadfa01e2900f0e5d515ad5506b26f6d65", null ], + [ "mi_check_owned", "group__analysis.html#ga628c237489c2679af84a4d0d143b3dd5", null ], + [ "mi_heap_check_owned", "group__analysis.html#ga0d67c1789faaa15ff366c024fcaf6377", null ], + [ "mi_heap_contains_block", "group__analysis.html#gaa862aa8ed8d57d84cae41fc1022d71af", null ], + [ "mi_heap_visit_blocks", "group__analysis.html#ga70c46687dc6e9dc98b232b02646f8bed", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/group__analysis_structmi__heap__area__t.js b/extlib/mimalloc/docs/group__analysis_structmi__heap__area__t.js new file mode 100644 index 0000000..2dbabc5 --- /dev/null +++ b/extlib/mimalloc/docs/group__analysis_structmi__heap__area__t.js @@ -0,0 +1,8 @@ +var group__analysis_structmi__heap__area__t = +[ + [ "block_size", "group__analysis.html#a332a6c14d736a99699d5453a1cb04b41", null ], + [ "blocks", "group__analysis.html#ae0085e6e1cf059a4eb7767e30e9991b8", null ], + [ "committed", "group__analysis.html#ab47526df656d8837ec3e97f11b83f835", null ], + [ "reserved", "group__analysis.html#ae848a3e6840414891035423948ca0383", null ], + [ "used", "group__analysis.html#ab820302c5cd0df133eb8e51650a008b4", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/group__cpp.html b/extlib/mimalloc/docs/group__cpp.html new file mode 100644 index 0000000..88c7588 --- /dev/null +++ b/extlib/mimalloc/docs/group__cpp.html @@ -0,0 +1,396 @@ + + + + + + + +mi-malloc: C++ wrappers + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
C++ wrappers
+
+
+ +

mi_ prefixed implementations of various allocation functions that use C++ semantics on out-of-memory, generally calling std::get_new_handler and raising a std::bad_alloc exception on failure. +More...

+ + + + + +

+Data Structures

struct  mi_stl_allocator< T >
 std::allocator implementation for mimalloc for use in STL containers. More...
 
+ + + + + + + + + + + + + + + + + + + + + + +

+Functions

void * mi_new (std::size_t n) noexcept(false)
 like mi_malloc(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception on failure. More...
 
void * mi_new_n (size_t count, size_t size) noexcept(false)
 like mi_mallocn(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception on failure. More...
 
void * mi_new_aligned (std::size_t n, std::align_val_t alignment) noexcept(false)
 like mi_malloc_aligned(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception on failure. More...
 
void * mi_new_nothrow (size_t n)
 like mi_malloc, but when out of memory, use std::get_new_handler but return NULL on failure. More...
 
void * mi_new_aligned_nothrow (size_t n, size_t alignment)
 like mi_malloc_aligned, but when out of memory, use std::get_new_handler but return NULL on failure. More...
 
void * mi_new_realloc (void *p, size_t newsize)
 like mi_realloc(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception on failure. More...
 
void * mi_new_reallocn (void *p, size_t newcount, size_t size)
 like mi_reallocn(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception on failure. More...
 
+

Detailed Description

+

mi_ prefixed implementations of various allocation functions that use C++ semantics on out-of-memory, generally calling std::get_new_handler and raising a std::bad_alloc exception on failure.

+

Note: use the mimalloc-new-delete.h header to override the new and delete operators globally. The wrappers here are mostly for convience for library writers that need to interface with mimalloc from C++.

+

Data Structure Documentation

+ +

◆ mi_stl_allocator

+ +
+
+ + + + +
struct mi_stl_allocator
+
+

template<class T>
+struct mi_stl_allocator< T >

+ +

std::allocator implementation for mimalloc for use in STL containers.

+

For example:

std::vector<int, mi_stl_allocator<int> > vec;
vec.push_back(1);
vec.pop_back();
+
+
+

Function Documentation

+ +

◆ mi_new()

+ +
+
+ + + + + +
+ + + + + + + + +
void* mi_new (std::size_t n)
+
+noexcept
+
+ +

like mi_malloc(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception on failure.

+ +
+
+ +

◆ mi_new_aligned()

+ +
+
+ + + + + +
+ + + + + + + + + + + + + + + + + + +
void* mi_new_aligned (std::size_t n,
std::align_val_t alignment 
)
+
+noexcept
+
+ +

like mi_malloc_aligned(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception on failure.

+ +
+
+ +

◆ mi_new_aligned_nothrow()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_new_aligned_nothrow (size_t n,
size_t alignment 
)
+
+ +

like mi_malloc_aligned, but when out of memory, use std::get_new_handler but return NULL on failure.

+ +
+
+ +

◆ mi_new_n()

+ +
+
+ + + + + +
+ + + + + + + + + + + + + + + + + + +
void* mi_new_n (size_t count,
size_t size 
)
+
+noexcept
+
+ +

like mi_mallocn(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception on failure.

+ +
+
+ +

◆ mi_new_nothrow()

+ +
+
+ + + + + + + + +
void* mi_new_nothrow (size_t n)
+
+ +

like mi_malloc, but when out of memory, use std::get_new_handler but return NULL on failure.

+ +
+
+ +

◆ mi_new_realloc()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_new_realloc (void * p,
size_t newsize 
)
+
+ +

like mi_realloc(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception on failure.

+ +
+
+ +

◆ mi_new_reallocn()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_new_reallocn (void * p,
size_t newcount,
size_t size 
)
+
+ +

like mi_reallocn(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception on failure.

+ +
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/group__cpp.js b/extlib/mimalloc/docs/group__cpp.js new file mode 100644 index 0000000..2070664 --- /dev/null +++ b/extlib/mimalloc/docs/group__cpp.js @@ -0,0 +1,11 @@ +var group__cpp = +[ + [ "mi_stl_allocator", "group__cpp.html#structmi__stl__allocator", null ], + [ "mi_new", "group__cpp.html#gaad048a9fce3d02c5909cd05c6ec24545", null ], + [ "mi_new_aligned", "group__cpp.html#gaef2c2bdb4f70857902d3c8903ac095f3", null ], + [ "mi_new_aligned_nothrow", "group__cpp.html#gab5e29558926d934c3f1cae8c815f942c", null ], + [ "mi_new_n", "group__cpp.html#gae7bc4f56cd57ed3359060ff4f38bda81", null ], + [ "mi_new_nothrow", "group__cpp.html#gaeaded64eda71ed6b1d569d3e723abc4a", null ], + [ "mi_new_realloc", "group__cpp.html#gaab78a32f55149e9fbf432d5288e38e1e", null ], + [ "mi_new_reallocn", "group__cpp.html#ga756f4b2bc6a7ecd0a90baea8e90c7907", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/group__extended.html b/extlib/mimalloc/docs/group__extended.html new file mode 100644 index 0000000..575288a --- /dev/null +++ b/extlib/mimalloc/docs/group__extended.html @@ -0,0 +1,884 @@ + + + + + + + +mi-malloc: Extended Functions + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
Extended Functions
+
+
+ +

Extended functionality. +More...

+ + + + + +

+Macros

#define MI_SMALL_SIZE_MAX
 Maximum size allowed for small allocations in mi_malloc_small and mi_zalloc_small (usually 128*sizeof(void*) (= 1KB on 64-bit systems)) More...
 
+ + + + + + + + + + +

+Typedefs

typedef void() mi_deferred_free_fun(bool force, unsigned long long heartbeat, void *arg)
 Type of deferred free functions. More...
 
typedef void() mi_output_fun(const char *msg, void *arg)
 Type of output functions. More...
 
typedef void() mi_error_fun(int err, void *arg)
 Type of error callback functions. More...
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

void * mi_malloc_small (size_t size)
 Allocate a small object. More...
 
void * mi_zalloc_small (size_t size)
 Allocate a zero initialized small object. More...
 
size_t mi_usable_size (void *p)
 Return the available bytes in a memory block. More...
 
size_t mi_good_size (size_t size)
 Return the used allocation size. More...
 
void mi_collect (bool force)
 Eagerly free memory. More...
 
void mi_stats_print (void *out)
 Deprecated. More...
 
void mi_stats_print_out (mi_output_fun *out, void *arg)
 Print the main statistics. More...
 
void mi_stats_reset (void)
 Reset statistics. More...
 
void mi_stats_merge (void)
 Merge thread local statistics with the main statistics and reset. More...
 
void mi_thread_init (void)
 Initialize mimalloc on a thread. More...
 
void mi_thread_done (void)
 Uninitialize mimalloc on a thread. More...
 
void mi_thread_stats_print_out (mi_output_fun *out, void *arg)
 Print out heap statistics for this thread. More...
 
void mi_register_deferred_free (mi_deferred_free_fun *deferred_free, void *arg)
 Register a deferred free function. More...
 
void mi_register_output (mi_output_fun *out, void *arg)
 Register an output function. More...
 
void mi_register_error (mi_error_fun *errfun, void *arg)
 Register an error callback function. More...
 
bool mi_is_in_heap_region (const void *p)
 Is a pointer part of our heap? More...
 
int mi_reserve_huge_os_pages_interleave (size_t pages, size_t numa_nodes, size_t timeout_msecs)
 Reserve pages of huge OS pages (1GiB) evenly divided over numa_nodes nodes, but stops after at most timeout_msecs seconds. More...
 
int mi_reserve_huge_os_pages_at (size_t pages, int numa_node, size_t timeout_msecs)
 Reserve pages of huge OS pages (1GiB) at a specific numa_node, but stops after at most timeout_msecs seconds. More...
 
bool mi_is_redirected ()
 Is the C runtime malloc API redirected? More...
 
+

Detailed Description

+

Extended functionality.

+

Macro Definition Documentation

+ +

◆ MI_SMALL_SIZE_MAX

+ +
+
+ + + + +
#define MI_SMALL_SIZE_MAX
+
+ +

Maximum size allowed for small allocations in mi_malloc_small and mi_zalloc_small (usually 128*sizeof(void*) (= 1KB on 64-bit systems))

+ +
+
+

Typedef Documentation

+ +

◆ mi_deferred_free_fun

+ +
+
+ + + + +
typedef void() mi_deferred_free_fun(bool force, unsigned long long heartbeat, void *arg)
+
+ +

Type of deferred free functions.

+
Parameters
+ + + + +
forceIf true all outstanding items should be freed.
heartbeatA monotonically increasing count.
argArgument that was passed at registration to hold extra state.
+
+
+
See also
mi_register_deferred_free
+ +
+
+ +

◆ mi_error_fun

+ +
+
+ + + + +
typedef void() mi_error_fun(int err, void *arg)
+
+ +

Type of error callback functions.

+
Parameters
+ + + +
errError code (see mi_register_error() for a complete list).
argArgument that was passed at registration to hold extra state.
+
+
+
See also
mi_register_error()
+ +
+
+ +

◆ mi_output_fun

+ +
+
+ + + + +
typedef void() mi_output_fun(const char *msg, void *arg)
+
+ +

Type of output functions.

+
Parameters
+ + + +
msgMessage to output.
argArgument that was passed at registration to hold extra state.
+
+
+
See also
mi_register_output()
+ +
+
+

Function Documentation

+ +

◆ mi_collect()

+ +
+
+ + + + + + + + +
void mi_collect (bool force)
+
+ +

Eagerly free memory.

+
Parameters
+ + +
forceIf true, aggressively return memory to the OS (can be expensive!)
+
+
+

Regular code should not have to call this function. It can be beneficial in very narrow circumstances; in particular, when a long running thread allocates a lot of blocks that are freed by other threads it may improve resource usage by calling this every once in a while.

+ +
+
+ +

◆ mi_good_size()

+ +
+
+ + + + + + + + +
size_t mi_good_size (size_t size)
+
+ +

Return the used allocation size.

+
Parameters
+ + +
sizeThe minimal required size in bytes.
+
+
+
Returns
the size n that will be allocated, where n >= size.
+

Generally, mi_usable_size(mi_malloc(size)) == mi_good_size(size). This can be used to reduce internal wasted space when allocating buffers for example.

+
See also
mi_usable_size()
+ +
+
+ +

◆ mi_is_in_heap_region()

+ +
+
+ + + + + + + + +
bool mi_is_in_heap_region (const void * p)
+
+ +

Is a pointer part of our heap?

+
Parameters
+ + +
pThe pointer to check.
+
+
+
Returns
true if this is a pointer into our heap. This function is relatively fast.
+ +
+
+ +

◆ mi_is_redirected()

+ +
+
+ + + + + + + +
bool mi_is_redirected ()
+
+ +

Is the C runtime malloc API redirected?

+
Returns
true if all malloc API calls are redirected to mimalloc.
+

Currenty only used on Windows.

+ +
+
+ +

◆ mi_malloc_small()

+ +
+
+ + + + + + + + +
void* mi_malloc_small (size_t size)
+
+ +

Allocate a small object.

+
Parameters
+ + +
sizeThe size in bytes, can be at most MI_SMALL_SIZE_MAX.
+
+
+
Returns
a pointer to newly allocated memory of at least size bytes, or NULL if out of memory. This function is meant for use in run-time systems for best performance and does not check if size was indeed small – use with care!
+ +
+
+ +

◆ mi_register_deferred_free()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_register_deferred_free (mi_deferred_free_fundeferred_free,
void * arg 
)
+
+ +

Register a deferred free function.

+
Parameters
+ + + +
deferred_freeAddress of a deferred free-ing function or NULL to unregister.
argArgument that will be passed on to the deferred free function.
+
+
+

Some runtime systems use deferred free-ing, for example when using reference counting to limit the worst case free time. Such systems can register (re-entrant) deferred free function to free more memory on demand. When the force parameter is true all possible memory should be freed. The per-thread heartbeat parameter is monotonically increasing and guaranteed to be deterministic if the program allocates deterministically. The deferred_free function is guaranteed to be called deterministically after some number of allocations (regardless of freeing or available free memory). At most one deferred_free function can be active.

+ +
+
+ +

◆ mi_register_error()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_register_error (mi_error_funerrfun,
void * arg 
)
+
+ +

Register an error callback function.

+
Parameters
+ + + +
errfunThe error function that is called on an error (use NULL for default)
argExtra argument that will be passed on to the error function.
+
+
+

The errfun function is called on an error in mimalloc after emitting an error message (through the output function). It as always legal to just return from the errfun function in which case allocation functions generally return NULL or ignore the condition. The default function only calls abort() when compiled in secure mode with an EFAULT error. The possible error codes are:

    +
  • EAGAIN: Double free was detected (only in debug and secure mode).
  • +
  • EFAULT: Corrupted free list or meta-data was detected (only in debug and secure mode).
  • +
  • ENOMEM: Not enough memory available to satisfy the request.
  • +
  • EOVERFLOW: Too large a request, for example in mi_calloc(), the count and size parameters are too large.
  • +
  • EINVAL: Trying to free or re-allocate an invalid pointer.
  • +
+ +
+
+ +

◆ mi_register_output()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_register_output (mi_output_funout,
void * arg 
)
+
+ +

Register an output function.

+
Parameters
+ + + +
outThe output function, use NULL to output to stderr.
argArgument that will be passed on to the output function.
+
+
+

The out function is called to output any information from mimalloc, like verbose or warning messages.

+ +
+
+ +

◆ mi_reserve_huge_os_pages_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int mi_reserve_huge_os_pages_at (size_t pages,
int numa_node,
size_t timeout_msecs 
)
+
+ +

Reserve pages of huge OS pages (1GiB) at a specific numa_node, but stops after at most timeout_msecs seconds.

+
Parameters
+ + + + +
pagesThe number of 1GiB pages to reserve.
numa_nodeThe NUMA node where the memory is reserved (start at 0).
timeout_msecsMaximum number of milli-seconds to try reserving, or 0 for no timeout.
+
+
+
Returns
0 if successfull, ENOMEM if running out of memory, or ETIMEDOUT if timed out.
+

The reserved memory is used by mimalloc to satisfy allocations. May quit before timeout_msecs are expired if it estimates it will take more than 1.5 times timeout_msecs. The time limit is needed because on some operating systems it can take a long time to reserve contiguous memory if the physical memory is fragmented.

+ +
+
+ +

◆ mi_reserve_huge_os_pages_interleave()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int mi_reserve_huge_os_pages_interleave (size_t pages,
size_t numa_nodes,
size_t timeout_msecs 
)
+
+ +

Reserve pages of huge OS pages (1GiB) evenly divided over numa_nodes nodes, but stops after at most timeout_msecs seconds.

+
Parameters
+ + + + +
pagesThe number of 1GiB pages to reserve.
numa_nodesThe number of nodes do evenly divide the pages over, or 0 for using the actual number of NUMA nodes.
timeout_msecsMaximum number of milli-seconds to try reserving, or 0 for no timeout.
+
+
+
Returns
0 if successfull, ENOMEM if running out of memory, or ETIMEDOUT if timed out.
+

The reserved memory is used by mimalloc to satisfy allocations. May quit before timeout_msecs are expired if it estimates it will take more than 1.5 times timeout_msecs. The time limit is needed because on some operating systems it can take a long time to reserve contiguous memory if the physical memory is fragmented.

+ +
+
+ +

◆ mi_stats_merge()

+ +
+
+ + + + + + + + +
void mi_stats_merge (void )
+
+ +

Merge thread local statistics with the main statistics and reset.

+ +
+
+ +

◆ mi_stats_print()

+ +
+
+ + + + + + + + +
void mi_stats_print (void * out)
+
+ +

Deprecated.

+
Parameters
+ + +
outIgnored, outputs to the registered output function or stderr by default.
+
+
+

Most detailed when using a debug build.

+ +
+
+ +

◆ mi_stats_print_out()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_stats_print_out (mi_output_funout,
void * arg 
)
+
+ +

Print the main statistics.

+
Parameters
+ + + +
outAn output function or NULL for the default.
argOptional argument passed to out (if not NULL)
+
+
+

Most detailed when using a debug build.

+ +
+
+ +

◆ mi_stats_reset()

+ +
+
+ + + + + + + + +
void mi_stats_reset (void )
+
+ +

Reset statistics.

+ +
+
+ +

◆ mi_thread_done()

+ +
+
+ + + + + + + + +
void mi_thread_done (void )
+
+ +

Uninitialize mimalloc on a thread.

+

Should not be used as on most systems (pthreads, windows) this is done automatically. Ensures that any memory that is not freed yet (but will be freed by other threads in the future) is properly handled.

+ +
+
+ +

◆ mi_thread_init()

+ +
+
+ + + + + + + + +
void mi_thread_init (void )
+
+ +

Initialize mimalloc on a thread.

+

Should not be used as on most systems (pthreads, windows) this is done automatically.

+ +
+
+ +

◆ mi_thread_stats_print_out()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_thread_stats_print_out (mi_output_funout,
void * arg 
)
+
+ +

Print out heap statistics for this thread.

+
Parameters
+ + + +
outAn output function or NULL for the default.
argOptional argument passed to out (if not NULL)
+
+
+

Most detailed when using a debug build.

+ +
+
+ +

◆ mi_usable_size()

+ +
+
+ + + + + + + + +
size_t mi_usable_size (void * p)
+
+ +

Return the available bytes in a memory block.

+
Parameters
+ + +
pPointer to previously allocated memory (or NULL)
+
+
+
Returns
Returns the available bytes in the memory block, or 0 if p was NULL.
+

The returned size can be used to call mi_expand successfully. The returned size is always at least equal to the allocated size of p, and, in the current design, should be less than 16.7% more.

+
See also
_msize (Windows)
+
+malloc_usable_size (Linux)
+
+mi_good_size()
+ +
+
+ +

◆ mi_zalloc_small()

+ +
+
+ + + + + + + + +
void* mi_zalloc_small (size_t size)
+
+ +

Allocate a zero initialized small object.

+
Parameters
+ + +
sizeThe size in bytes, can be at most MI_SMALL_SIZE_MAX.
+
+
+
Returns
a pointer to newly allocated zero-initialized memory of at least size bytes, or NULL if out of memory. This function is meant for use in run-time systems for best performance and does not check if size was indeed small – use with care!
+ +
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/group__extended.js b/extlib/mimalloc/docs/group__extended.js new file mode 100644 index 0000000..594cadb --- /dev/null +++ b/extlib/mimalloc/docs/group__extended.js @@ -0,0 +1,26 @@ +var group__extended = +[ + [ "MI_SMALL_SIZE_MAX", "group__extended.html#ga1ea64283508718d9d645c38efc2f4305", null ], + [ "mi_deferred_free_fun", "group__extended.html#ga299dae78d25ce112e384a98b7309c5be", null ], + [ "mi_error_fun", "group__extended.html#ga251d369cda3f1c2a955c555486ed90e5", null ], + [ "mi_output_fun", "group__extended.html#gad823d23444a4b77a40f66bf075a98a0c", null ], + [ "mi_collect", "group__extended.html#ga421430e2226d7d468529cec457396756", null ], + [ "mi_good_size", "group__extended.html#gac057927cd06c854b45fe7847e921bd47", null ], + [ "mi_is_in_heap_region", "group__extended.html#ga5f071b10d4df1c3658e04e7fd67a94e6", null ], + [ "mi_is_redirected", "group__extended.html#gaad25050b19f30cd79397b227e0157a3f", null ], + [ "mi_malloc_small", "group__extended.html#ga7136c2e55cb22c98ecf95d08d6debb99", null ], + [ "mi_register_deferred_free", "group__extended.html#ga3460a6ca91af97be4058f523d3cb8ece", null ], + [ "mi_register_error", "group__extended.html#gaa1d55e0e894be240827e5d87ec3a1f45", null ], + [ "mi_register_output", "group__extended.html#gae5b17ff027cd2150b43a33040250cf3f", null ], + [ "mi_reserve_huge_os_pages_at", "group__extended.html#ga7795a13d20087447281858d2c771cca1", null ], + [ "mi_reserve_huge_os_pages_interleave", "group__extended.html#ga3132f521fb756fc0e8ec0b74fb58df50", null ], + [ "mi_stats_merge", "group__extended.html#ga854b1de8cb067c7316286c28b2fcd3d1", null ], + [ "mi_stats_print", "group__extended.html#ga2d126e5c62d3badc35445e5d84166df2", null ], + [ "mi_stats_print_out", "group__extended.html#ga537f13b299ddf801e49a5a94fde02c79", null ], + [ "mi_stats_reset", "group__extended.html#ga3bb8468b8cfcc6e2a61d98aee85c5f99", null ], + [ "mi_thread_done", "group__extended.html#ga0ae4581e85453456a0d658b2b98bf7bf", null ], + [ "mi_thread_init", "group__extended.html#gaf8e73efc2cbca9ebfdfb166983a04c17", null ], + [ "mi_thread_stats_print_out", "group__extended.html#gab1dac8476c46cb9eecab767eb40c1525", null ], + [ "mi_usable_size", "group__extended.html#ga089c859d9eddc5f9b4bd946cd53cebee", null ], + [ "mi_zalloc_small", "group__extended.html#ga220f29f40a44404b0061c15bc1c31152", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/group__heap.html b/extlib/mimalloc/docs/group__heap.html new file mode 100644 index 0000000..1a38c93 --- /dev/null +++ b/extlib/mimalloc/docs/group__heap.html @@ -0,0 +1,1080 @@ + + + + + + + +mi-malloc: Heap Allocation + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
Heap Allocation
+
+
+ +

First-class heaps that can be destroyed in one go. +More...

+ + + + + +

+Typedefs

typedef struct mi_heap_s mi_heap_t
 Type of first-class heaps. More...
 
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

mi_heap_tmi_heap_new ()
 Create a new heap that can be used for allocation. More...
 
void mi_heap_delete (mi_heap_t *heap)
 Delete a previously allocated heap. More...
 
void mi_heap_destroy (mi_heap_t *heap)
 Destroy a heap, freeing all its still allocated blocks. More...
 
mi_heap_tmi_heap_set_default (mi_heap_t *heap)
 Set the default heap to use for mi_malloc() et al. More...
 
mi_heap_tmi_heap_get_default ()
 Get the default heap that is used for mi_malloc() et al. More...
 
mi_heap_tmi_heap_get_backing ()
 Get the backing heap. More...
 
void mi_heap_collect (mi_heap_t *heap, bool force)
 Release outstanding resources in a specific heap. More...
 
void * mi_heap_malloc (mi_heap_t *heap, size_t size)
 Allocate in a specific heap. More...
 
void * mi_heap_malloc_small (mi_heap_t *heap, size_t size)
 Allocate a small object in a specific heap. More...
 
void * mi_heap_zalloc (mi_heap_t *heap, size_t size)
 Allocate zero-initialized in a specific heap. More...
 
void * mi_heap_calloc (mi_heap_t *heap, size_t count, size_t size)
 Allocate count zero-initialized elements in a specific heap. More...
 
void * mi_heap_mallocn (mi_heap_t *heap, size_t count, size_t size)
 Allocate count elements in a specific heap. More...
 
char * mi_heap_strdup (mi_heap_t *heap, const char *s)
 Duplicate a string in a specific heap. More...
 
char * mi_heap_strndup (mi_heap_t *heap, const char *s, size_t n)
 Duplicate a string of at most length n in a specific heap. More...
 
char * mi_heap_realpath (mi_heap_t *heap, const char *fname, char *resolved_name)
 Resolve a file path name using a specific heap to allocate the result. More...
 
void * mi_heap_realloc (mi_heap_t *heap, void *p, size_t newsize)
 
void * mi_heap_reallocn (mi_heap_t *heap, void *p, size_t count, size_t size)
 
void * mi_heap_reallocf (mi_heap_t *heap, void *p, size_t newsize)
 
void * mi_heap_malloc_aligned (mi_heap_t *heap, size_t size, size_t alignment)
 
void * mi_heap_malloc_aligned_at (mi_heap_t *heap, size_t size, size_t alignment, size_t offset)
 
void * mi_heap_zalloc_aligned (mi_heap_t *heap, size_t size, size_t alignment)
 
void * mi_heap_zalloc_aligned_at (mi_heap_t *heap, size_t size, size_t alignment, size_t offset)
 
void * mi_heap_calloc_aligned (mi_heap_t *heap, size_t count, size_t size, size_t alignment)
 
void * mi_heap_calloc_aligned_at (mi_heap_t *heap, size_t count, size_t size, size_t alignment, size_t offset)
 
void * mi_heap_realloc_aligned (mi_heap_t *heap, void *p, size_t newsize, size_t alignment)
 
void * mi_heap_realloc_aligned_at (mi_heap_t *heap, void *p, size_t newsize, size_t alignment, size_t offset)
 
+

Detailed Description

+

First-class heaps that can be destroyed in one go.

+

Typedef Documentation

+ +

◆ mi_heap_t

+ +
+
+ + + + +
typedef struct mi_heap_s mi_heap_t
+
+ +

Type of first-class heaps.

+

A heap can only be used for (re)allocation in the thread that created this heap! Any allocated blocks can be freed by any other thread though.

+ +
+
+

Function Documentation

+ +

◆ mi_heap_calloc()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_calloc (mi_heap_theap,
size_t count,
size_t size 
)
+
+ +

Allocate count zero-initialized elements in a specific heap.

+
See also
mi_calloc()
+ +
+
+ +

◆ mi_heap_calloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_calloc_aligned (mi_heap_theap,
size_t count,
size_t size,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_heap_calloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_calloc_aligned_at (mi_heap_theap,
size_t count,
size_t size,
size_t alignment,
size_t offset 
)
+
+ +
+
+ +

◆ mi_heap_collect()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_heap_collect (mi_heap_theap,
bool force 
)
+
+ +

Release outstanding resources in a specific heap.

+ +
+
+ +

◆ mi_heap_delete()

+ +
+
+ + + + + + + + +
void mi_heap_delete (mi_heap_theap)
+
+ +

Delete a previously allocated heap.

+

This will release resources and migrate any still allocated blocks in this heap (efficienty) to the default heap.

+

If heap is the default heap, the default heap is set to the backing heap.

+ +
+
+ +

◆ mi_heap_destroy()

+ +
+
+ + + + + + + + +
void mi_heap_destroy (mi_heap_theap)
+
+ +

Destroy a heap, freeing all its still allocated blocks.

+

Use with care as this will free all blocks still allocated in the heap. However, this can be a very efficient way to free all heap memory in one go.

+

If heap is the default heap, the default heap is set to the backing heap.

+ +
+
+ +

◆ mi_heap_get_backing()

+ +
+
+ + + + + + + +
mi_heap_t* mi_heap_get_backing ()
+
+ +

Get the backing heap.

+

The backing heap is the initial default heap for a thread and always available for allocations. It cannot be destroyed or deleted except by exiting the thread.

+ +
+
+ +

◆ mi_heap_get_default()

+ +
+
+ + + + + + + +
mi_heap_t* mi_heap_get_default ()
+
+ +

Get the default heap that is used for mi_malloc() et al.

+
Returns
The current default heap.
+ +
+
+ +

◆ mi_heap_malloc()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_heap_malloc (mi_heap_theap,
size_t size 
)
+
+ +

Allocate in a specific heap.

+
See also
mi_malloc()
+ +
+
+ +

◆ mi_heap_malloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_malloc_aligned (mi_heap_theap,
size_t size,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_heap_malloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_malloc_aligned_at (mi_heap_theap,
size_t size,
size_t alignment,
size_t offset 
)
+
+ +
+
+ +

◆ mi_heap_malloc_small()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_heap_malloc_small (mi_heap_theap,
size_t size 
)
+
+ +

Allocate a small object in a specific heap.

+

size must be smaller or equal to MI_SMALL_SIZE_MAX().

See also
mi_malloc()
+ +
+
+ +

◆ mi_heap_mallocn()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_mallocn (mi_heap_theap,
size_t count,
size_t size 
)
+
+ +

Allocate count elements in a specific heap.

+
See also
mi_mallocn()
+ +
+
+ +

◆ mi_heap_new()

+ +
+
+ + + + + + + +
mi_heap_t* mi_heap_new ()
+
+ +

Create a new heap that can be used for allocation.

+ +
+
+ +

◆ mi_heap_realloc()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_realloc (mi_heap_theap,
void * p,
size_t newsize 
)
+
+ +
+
+ +

◆ mi_heap_realloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_realloc_aligned (mi_heap_theap,
void * p,
size_t newsize,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_heap_realloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_realloc_aligned_at (mi_heap_theap,
void * p,
size_t newsize,
size_t alignment,
size_t offset 
)
+
+ +
+
+ +

◆ mi_heap_reallocf()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_reallocf (mi_heap_theap,
void * p,
size_t newsize 
)
+
+ +
+
+ +

◆ mi_heap_reallocn()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_reallocn (mi_heap_theap,
void * p,
size_t count,
size_t size 
)
+
+ +
+
+ +

◆ mi_heap_realpath()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
char* mi_heap_realpath (mi_heap_theap,
const char * fname,
char * resolved_name 
)
+
+ +

Resolve a file path name using a specific heap to allocate the result.

+
See also
mi_realpath()
+ +
+
+ +

◆ mi_heap_set_default()

+ +
+
+ + + + + + + + +
mi_heap_t* mi_heap_set_default (mi_heap_theap)
+
+ +

Set the default heap to use for mi_malloc() et al.

+
Parameters
+ + +
heapThe new default heap.
+
+
+
Returns
The previous default heap.
+ +
+
+ +

◆ mi_heap_strdup()

+ +
+
+ + + + + + + + + + + + + + + + + + +
char* mi_heap_strdup (mi_heap_theap,
const char * s 
)
+
+ +

Duplicate a string in a specific heap.

+
See also
mi_strdup()
+ +
+
+ +

◆ mi_heap_strndup()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
char* mi_heap_strndup (mi_heap_theap,
const char * s,
size_t n 
)
+
+ +

Duplicate a string of at most length n in a specific heap.

+
See also
mi_strndup()
+ +
+
+ +

◆ mi_heap_zalloc()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_heap_zalloc (mi_heap_theap,
size_t size 
)
+
+ +

Allocate zero-initialized in a specific heap.

+
See also
mi_zalloc()
+ +
+
+ +

◆ mi_heap_zalloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_zalloc_aligned (mi_heap_theap,
size_t size,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_heap_zalloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_zalloc_aligned_at (mi_heap_theap,
size_t size,
size_t alignment,
size_t offset 
)
+
+ +
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/group__heap.js b/extlib/mimalloc/docs/group__heap.js new file mode 100644 index 0000000..13d1377 --- /dev/null +++ b/extlib/mimalloc/docs/group__heap.js @@ -0,0 +1,30 @@ +var group__heap = +[ + [ "mi_heap_t", "group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2", null ], + [ "mi_heap_calloc", "group__heap.html#gaa6702b3c48e9e53e50e81b36f5011d55", null ], + [ "mi_heap_calloc_aligned", "group__heap.html#ga4af03a6e2b93fae77424d93f889705c3", null ], + [ "mi_heap_calloc_aligned_at", "group__heap.html#ga08ca6419a5c057a4d965868998eef487", null ], + [ "mi_heap_collect", "group__heap.html#ga7922f7495cde30b1984d0e6072419298", null ], + [ "mi_heap_delete", "group__heap.html#ga2ab1af8d438819b55319c7ef51d1e409", null ], + [ "mi_heap_destroy", "group__heap.html#ga9f9c0844edb9717f4feacd79116b8e0d", null ], + [ "mi_heap_get_backing", "group__heap.html#ga5d03fbe062ffcf38f0f417fd968357fc", null ], + [ "mi_heap_get_default", "group__heap.html#ga8db4cbb87314a989a9a187464d6b5e05", null ], + [ "mi_heap_malloc", "group__heap.html#ga9cbed01e42c0647907295de92c3fa296", null ], + [ "mi_heap_malloc_aligned", "group__heap.html#gab5b87e1805306f70df38789fcfcf6653", null ], + [ "mi_heap_malloc_aligned_at", "group__heap.html#ga23acd7680fb0976dde3783254c6c874b", null ], + [ "mi_heap_malloc_small", "group__heap.html#gaa1a1c7a1f4da6826b5a25b70ef878368", null ], + [ "mi_heap_mallocn", "group__heap.html#ga851da6c43fe0b71c1376cee8aef90db0", null ], + [ "mi_heap_new", "group__heap.html#ga766f672ba56f2fbfeb9d9dbb0b7f6b11", null ], + [ "mi_heap_realloc", "group__heap.html#gaaef3395f66be48f37bdc8322509c5d81", null ], + [ "mi_heap_realloc_aligned", "group__heap.html#gafc603b696bd14cae6da28658f950d98c", null ], + [ "mi_heap_realloc_aligned_at", "group__heap.html#gaf96c788a1bf553fe2d371de9365e047c", null ], + [ "mi_heap_reallocf", "group__heap.html#ga4a21070eb4e7cce018133c8d5f4b0527", null ], + [ "mi_heap_reallocn", "group__heap.html#gac74e94ad9b0c9b57c1c4d88b8825b7a8", null ], + [ "mi_heap_realpath", "group__heap.html#ga00e95ba1e01acac3cfd95bb7a357a6f0", null ], + [ "mi_heap_set_default", "group__heap.html#gab8631ec88c8d26641b68b5d25dcd4422", null ], + [ "mi_heap_strdup", "group__heap.html#ga139d6b09dbf50c3c2523d0f4d1cfdeb5", null ], + [ "mi_heap_strndup", "group__heap.html#ga8e3dbd46650dd26573cf307a2c8f1f5a", null ], + [ "mi_heap_zalloc", "group__heap.html#ga903104592c8ed53417a3762da6241133", null ], + [ "mi_heap_zalloc_aligned", "group__heap.html#gaa450a59c6c7ae5fdbd1c2b80a8329ef0", null ], + [ "mi_heap_zalloc_aligned_at", "group__heap.html#ga45fb43a62776fbebbdf1edd99b527954", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/group__malloc.html b/extlib/mimalloc/docs/group__malloc.html new file mode 100644 index 0000000..224c4b0 --- /dev/null +++ b/extlib/mimalloc/docs/group__malloc.html @@ -0,0 +1,644 @@ + + + + + + + +mi-malloc: Basic Allocation + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
Basic Allocation
+
+
+ +

The basic allocation interface. +More...

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

void mi_free (void *p)
 Free previously allocated memory. More...
 
void * mi_malloc (size_t size)
 Allocate size bytes. More...
 
void * mi_zalloc (size_t size)
 Allocate zero-initialized size bytes. More...
 
void * mi_calloc (size_t count, size_t size)
 Allocate zero-initialized count elements of size bytes. More...
 
void * mi_realloc (void *p, size_t newsize)
 Re-allocate memory to newsize bytes. More...
 
void * mi_recalloc (void *p, size_t count, size_t size)
 Re-allocate memory to count elements of size bytes, with extra memory initialized to zero. More...
 
void * mi_expand (void *p, size_t newsize)
 Try to re-allocate memory to newsize bytes in place. More...
 
void * mi_mallocn (size_t count, size_t size)
 Allocate count elements of size bytes. More...
 
void * mi_reallocn (void *p, size_t count, size_t size)
 Re-allocate memory to count elements of size bytes. More...
 
void * mi_reallocf (void *p, size_t newsize)
 Re-allocate memory to newsize bytes,. More...
 
char * mi_strdup (const char *s)
 Allocate and duplicate a string. More...
 
char * mi_strndup (const char *s, size_t n)
 Allocate and duplicate a string up to n bytes. More...
 
char * mi_realpath (const char *fname, char *resolved_name)
 Resolve a file path name. More...
 
+

Detailed Description

+

The basic allocation interface.

+

Function Documentation

+ +

◆ mi_calloc()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_calloc (size_t count,
size_t size 
)
+
+ +

Allocate zero-initialized count elements of size bytes.

+
Parameters
+ + + +
countnumber of elements.
sizesize of each element.
+
+
+
Returns
pointer to the allocated memory of size*count bytes, or NULL if either out of memory or when count*size overflows.
+

Returns a unique pointer if called with either size or count of 0.

See also
mi_zalloc()
+ +
+
+ +

◆ mi_expand()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_expand (void * p,
size_t newsize 
)
+
+ +

Try to re-allocate memory to newsize bytes in place.

+
Parameters
+ + + +
ppointer to previously allocated memory (or NULL).
newsizethe new required size in bytes.
+
+
+
Returns
pointer to the re-allocated memory of newsize bytes (always equal to p), or NULL if either out of memory or if the memory could not be expanded in place. If NULL is returned, the pointer p is not freed. Otherwise the original pointer is returned as the reallocated result since it fits in-place with the new size. If newsize is larger than the original size allocated for p, the bytes after size are uninitialized.
+ +
+
+ +

◆ mi_free()

+ +
+
+ + + + + + + + +
void mi_free (void * p)
+
+ +

Free previously allocated memory.

+

The pointer p must have been allocated before (or be NULL).

Parameters
+ + +
ppointer to free, or NULL.
+
+
+ +
+
+ +

◆ mi_malloc()

+ +
+
+ + + + + + + + +
void* mi_malloc (size_t size)
+
+ +

Allocate size bytes.

+
Parameters
+ + +
sizenumber of bytes to allocate.
+
+
+
Returns
pointer to the allocated memory or NULL if out of memory. Returns a unique pointer if called with size 0.
+ +
+
+ +

◆ mi_mallocn()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_mallocn (size_t count,
size_t size 
)
+
+ +

Allocate count elements of size bytes.

+
Parameters
+ + + +
countThe number of elements.
sizeThe size of each element.
+
+
+
Returns
A pointer to a block of count * size bytes, or NULL if out of memory or if count * size overflows.
+

If there is no overflow, it behaves exactly like mi_malloc(p,count*size).

See also
mi_calloc()
+
+mi_zallocn()
+ +
+
+ +

◆ mi_realloc()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_realloc (void * p,
size_t newsize 
)
+
+ +

Re-allocate memory to newsize bytes.

+
Parameters
+ + + +
ppointer to previously allocated memory (or NULL).
newsizethe new required size in bytes.
+
+
+
Returns
pointer to the re-allocated memory of newsize bytes, or NULL if out of memory. If NULL is returned, the pointer p is not freed. Otherwise the original pointer is either freed or returned as the reallocated result (in case it fits in-place with the new size). If the pointer p is NULL, it behaves as mi_malloc(newsize). If newsize is larger than the original size allocated for p, the bytes after size are uninitialized.
+ +
+
+ +

◆ mi_reallocf()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_reallocf (void * p,
size_t newsize 
)
+
+ +

Re-allocate memory to newsize bytes,.

+
Parameters
+ + + +
ppointer to previously allocated memory (or NULL).
newsizethe new required size in bytes.
+
+
+
Returns
pointer to the re-allocated memory of newsize bytes, or NULL if out of memory.
+

In contrast to mi_realloc(), if NULL is returned, the original pointer p is freed (if it was not NULL itself). Otherwise the original pointer is either freed or returned as the reallocated result (in case it fits in-place with the new size). If the pointer p is NULL, it behaves as mi_malloc(newsize). If newsize is larger than the original size allocated for p, the bytes after size are uninitialized.

+
See also
reallocf (on BSD)
+ +
+
+ +

◆ mi_reallocn()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_reallocn (void * p,
size_t count,
size_t size 
)
+
+ +

Re-allocate memory to count elements of size bytes.

+
Parameters
+ + + + +
pPointer to a previously allocated block (or NULL).
countThe number of elements.
sizeThe size of each element.
+
+
+
Returns
A pointer to a re-allocated block of count * size bytes, or NULL if out of memory or if count * size overflows.
+

If there is no overflow, it behaves exactly like mi_realloc(p,count*size).

See also
reallocarray() (on BSD)
+ +
+
+ +

◆ mi_realpath()

+ +
+
+ + + + + + + + + + + + + + + + + + +
char* mi_realpath (const char * fname,
char * resolved_name 
)
+
+ +

Resolve a file path name.

+
Parameters
+ + + +
fnameFile name.
resolved_nameShould be NULL (but can also point to a buffer of at least PATH_MAX bytes).
+
+
+
Returns
If successful a pointer to the resolved absolute file name, or NULL on failure (with errno set to the error code).
+

If resolved_name was NULL, the returned result should be freed with mi_free().

+

Replacement for the standard realpath() such that mi_free() can be used on the returned result (if resolved_name was NULL).

+ +
+
+ +

◆ mi_recalloc()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void * mi_recalloc (void * p,
size_t count,
size_t size 
)
+
+ +

Re-allocate memory to count elements of size bytes, with extra memory initialized to zero.

+
Parameters
+ + + + +
pPointer to a previously allocated block (or NULL).
countThe number of elements.
sizeThe size of each element.
+
+
+
Returns
A pointer to a re-allocated block of count * size bytes, or NULL if out of memory or if count * size overflows.
+

If there is no overflow, it behaves exactly like mi_rezalloc(p,count*size).

See also
mi_reallocn()
+
+recallocarray() (on BSD).
+ +
+
+ +

◆ mi_strdup()

+ +
+
+ + + + + + + + +
char* mi_strdup (const char * s)
+
+ +

Allocate and duplicate a string.

+
Parameters
+ + +
sstring to duplicate (or NULL).
+
+
+
Returns
a pointer to newly allocated memory initialized to string s, or NULL if either out of memory or if s is NULL.
+

Replacement for the standard strdup() such that mi_free() can be used on the returned result.

+ +
+
+ +

◆ mi_strndup()

+ +
+
+ + + + + + + + + + + + + + + + + + +
char* mi_strndup (const char * s,
size_t n 
)
+
+ +

Allocate and duplicate a string up to n bytes.

+
Parameters
+ + + +
sstring to duplicate (or NULL).
nmaximum number of bytes to copy (excluding the terminating zero).
+
+
+
Returns
a pointer to newly allocated memory initialized to string s up to the first n bytes (and always zero terminated), or NULL if either out of memory or if s is NULL.
+

Replacement for the standard strndup() such that mi_free() can be used on the returned result.

+ +
+
+ +

◆ mi_zalloc()

+ +
+
+ + + + + + + + +
void* mi_zalloc (size_t size)
+
+ +

Allocate zero-initialized size bytes.

+
Parameters
+ + +
sizeThe size in bytes.
+
+
+
Returns
Pointer to newly allocated zero initialized memory, or NULL if out of memory.
+ +
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/group__malloc.js b/extlib/mimalloc/docs/group__malloc.js new file mode 100644 index 0000000..7293ffa --- /dev/null +++ b/extlib/mimalloc/docs/group__malloc.js @@ -0,0 +1,16 @@ +var group__malloc = +[ + [ "mi_calloc", "group__malloc.html#ga97fedb4f7107c592fd7f0f0a8949a57d", null ], + [ "mi_expand", "group__malloc.html#gaaee66a1d483c3e28f585525fb96707e4", null ], + [ "mi_free", "group__malloc.html#gaf2c7b89c327d1f60f59e68b9ea644d95", null ], + [ "mi_malloc", "group__malloc.html#ga3406e8b168bc74c8637b11571a6da83a", null ], + [ "mi_mallocn", "group__malloc.html#ga0b05e2bf0f73e7401ae08597ff782ac6", null ], + [ "mi_realloc", "group__malloc.html#gaf11eb497da57bdfb2de65eb191c69db6", null ], + [ "mi_reallocf", "group__malloc.html#gafe68ac7c5e24a65cd55c9d6b152211a0", null ], + [ "mi_reallocn", "group__malloc.html#ga61d57b4144ba24fba5c1e9b956d13853", null ], + [ "mi_realpath", "group__malloc.html#ga08cec32dd5bbe7da91c78d19f1b5bebe", null ], + [ "mi_recalloc", "group__malloc.html#ga23a0fbb452b5dce8e31fab1a1958cacc", null ], + [ "mi_strdup", "group__malloc.html#gac7cffe13f1f458ed16789488bf92b9b2", null ], + [ "mi_strndup", "group__malloc.html#gaaabf971c2571891433477e2d21a35266", null ], + [ "mi_zalloc", "group__malloc.html#gafdd9d8bb2986e668ba9884f28af38000", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/group__options.html b/extlib/mimalloc/docs/group__options.html new file mode 100644 index 0000000..9425765 --- /dev/null +++ b/extlib/mimalloc/docs/group__options.html @@ -0,0 +1,406 @@ + + + + + + + +mi-malloc: Runtime Options + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
Runtime Options
+
+
+ +

Set runtime behavior. +More...

+ + + + + +

+Enumerations

enum  mi_option_t {
+  mi_option_show_errors, +mi_option_show_stats, +mi_option_verbose, +mi_option_eager_commit, +
+  mi_option_eager_region_commit, +mi_option_large_os_pages, +mi_option_reserve_huge_os_pages, +mi_option_segment_cache, +
+  mi_option_page_reset, +mi_option_segment_reset, +mi_option_reset_delay, +mi_option_use_numa_nodes, +
+  mi_option_reset_decommits, +mi_option_eager_commit_delay, +mi_option_os_tag, +_mi_option_last +
+ }
 Runtime options. More...
 
+ + + + + + + + + + + + + + + + + +

+Functions

bool mi_option_is_enabled (mi_option_t option)
 
void mi_option_enable (mi_option_t option)
 
void mi_option_disable (mi_option_t option)
 
void mi_option_set_enabled (mi_option_t option, bool enable)
 
void mi_option_set_enabled_default (mi_option_t option, bool enable)
 
long mi_option_get (mi_option_t option)
 
void mi_option_set (mi_option_t option, long value)
 
void mi_option_set_default (mi_option_t option, long value)
 
+

Detailed Description

+

Set runtime behavior.

+

Enumeration Type Documentation

+ +

◆ mi_option_t

+ +
+
+ + + + +
enum mi_option_t
+
+ +

Runtime options.

+ + + + + + + + + + + + + + + + + +
Enumerator
mi_option_show_errors 

Print error messages to stderr.

+
mi_option_show_stats 

Print statistics to stderr when the program is done.

+
mi_option_verbose 

Print verbose messages to stderr.

+
mi_option_eager_commit 

Eagerly commit segments (4MiB) (enabled by default).

+
mi_option_eager_region_commit 

Eagerly commit large (256MiB) memory regions (enabled by default, except on Windows)

+
mi_option_large_os_pages 

Use large OS pages (2MiB in size) if possible.

+
mi_option_reserve_huge_os_pages 

The number of huge OS pages (1GiB in size) to reserve at the start of the program.

+
mi_option_segment_cache 

The number of segments per thread to keep cached.

+
mi_option_page_reset 

Reset page memory after mi_option_reset_delay milliseconds when it becomes free.

+
mi_option_segment_reset 

Experimental.

+
mi_option_reset_delay 

Delay in milli-seconds before resetting a page (100ms by default)

+
mi_option_use_numa_nodes 

Pretend there are at most N NUMA nodes.

+
mi_option_reset_decommits 

Experimental.

+
mi_option_eager_commit_delay 

Experimental.

+
mi_option_os_tag 

OS tag to assign to mimalloc'd memory.

+
_mi_option_last 
+ +
+
+

Function Documentation

+ +

◆ mi_option_disable()

+ +
+
+ + + + + + + + +
void mi_option_disable (mi_option_t option)
+
+ +
+
+ +

◆ mi_option_enable()

+ +
+
+ + + + + + + + +
void mi_option_enable (mi_option_t option)
+
+ +
+
+ +

◆ mi_option_get()

+ +
+
+ + + + + + + + +
long mi_option_get (mi_option_t option)
+
+ +
+
+ +

◆ mi_option_is_enabled()

+ +
+
+ + + + + + + + +
bool mi_option_is_enabled (mi_option_t option)
+
+ +
+
+ +

◆ mi_option_set()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_option_set (mi_option_t option,
long value 
)
+
+ +
+
+ +

◆ mi_option_set_default()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_option_set_default (mi_option_t option,
long value 
)
+
+ +
+
+ +

◆ mi_option_set_enabled()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_option_set_enabled (mi_option_t option,
bool enable 
)
+
+ +
+
+ +

◆ mi_option_set_enabled_default()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_option_set_enabled_default (mi_option_t option,
bool enable 
)
+
+ +
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/group__options.js b/extlib/mimalloc/docs/group__options.js new file mode 100644 index 0000000..9aaf231 --- /dev/null +++ b/extlib/mimalloc/docs/group__options.js @@ -0,0 +1,29 @@ +var group__options = +[ + [ "mi_option_t", "group__options.html#gafebf7ed116adb38ae5218bc3ce06884c", [ + [ "mi_option_show_errors", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafbf4822e5c00732c5984b32a032837f0", null ], + [ "mi_option_show_stats", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0957ef73b2550764b4840edf48422fda", null ], + [ "mi_option_verbose", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca7c8b7bf5281c581bad64f5daa6442777", null ], + [ "mi_option_eager_commit", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca1e8de72c93da7ff22d91e1e27b52ac2b", null ], + [ "mi_option_eager_region_commit", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca32ce97ece29f69e82579679cf8a307ad", null ], + [ "mi_option_large_os_pages", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4192d491200d0055df0554d4cf65054e", null ], + [ "mi_option_reserve_huge_os_pages", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884caca7ed041be3b0b9d0b82432c7bf41af2", null ], + [ "mi_option_segment_cache", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca2ecbe7ef32f5c84de3739aa4f0b805a1", null ], + [ "mi_option_page_reset", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cada854dd272c66342f18a93ee254a2968", null ], + [ "mi_option_segment_reset", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafb121d30d87591850d5410ccc3a95c6d", null ], + [ "mi_option_reset_delay", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca154fe170131d5212cff57e22b99523c5", null ], + [ "mi_option_use_numa_nodes", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0ac33a18f6b659fcfaf44efb0bab1b74", null ], + [ "mi_option_reset_decommits", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cac81ee965b130fa81238913a3c239d536", null ], + [ "mi_option_eager_commit_delay", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca17a190c25be381142d87e0468c4c068c", null ], + [ "mi_option_os_tag", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4b74ae2a69e445de6c2361b73c1d14bf", null ], + [ "_mi_option_last", "group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca5b4357b74be0d87568036c32eb1a2e4a", null ] + ] ], + [ "mi_option_disable", "group__options.html#gaebf6ff707a2e688ebb1a2296ca564054", null ], + [ "mi_option_enable", "group__options.html#ga04180ae41b0d601421dd62ced40ca050", null ], + [ "mi_option_get", "group__options.html#ga7e8af195cc81d3fa64ccf2662caa565a", null ], + [ "mi_option_is_enabled", "group__options.html#ga459ad98f18b3fc9275474807fe0ca188", null ], + [ "mi_option_set", "group__options.html#gaf84921c32375e25754dc2ee6a911fa60", null ], + [ "mi_option_set_default", "group__options.html#ga7ef623e440e6e5545cb08c94e71e4b90", null ], + [ "mi_option_set_enabled", "group__options.html#ga9a13d05fcb77489cb06d4d017ebd8bed", null ], + [ "mi_option_set_enabled_default", "group__options.html#ga65518b69ec5d32336b50e07f74b3f629", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/group__posix.html b/extlib/mimalloc/docs/group__posix.html new file mode 100644 index 0000000..fe3a88e --- /dev/null +++ b/extlib/mimalloc/docs/group__posix.html @@ -0,0 +1,496 @@ + + + + + + + +mi-malloc: Posix + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
Posix
+
+
+ +

mi_ prefixed implementations of various Posix, Unix, and C++ allocation functions. +More...

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

size_t mi_malloc_size (const void *p)
 
size_t mi_malloc_usable_size (const void *p)
 
void mi_cfree (void *p)
 Just as free but also checks if the pointer p belongs to our heap. More...
 
int mi_posix_memalign (void **p, size_t alignment, size_t size)
 
int mi__posix_memalign (void **p, size_t alignment, size_t size)
 
void * mi_memalign (size_t alignment, size_t size)
 
void * mi_valloc (size_t size)
 
void * mi_pvalloc (size_t size)
 
void * mi_aligned_alloc (size_t alignment, size_t size)
 
void * mi_reallocarray (void *p, size_t count, size_t size)
 
void mi_free_size (void *p, size_t size)
 
void mi_free_size_aligned (void *p, size_t size, size_t alignment)
 
void mi_free_aligned (void *p, size_t alignment)
 
+

Detailed Description

+

mi_ prefixed implementations of various Posix, Unix, and C++ allocation functions.

+

Defined for convenience as all redirect to the regular mimalloc API.

+

Function Documentation

+ +

◆ mi__posix_memalign()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int mi__posix_memalign (void ** p,
size_t alignment,
size_t size 
)
+
+ +
+
+ +

◆ mi_aligned_alloc()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_aligned_alloc (size_t alignment,
size_t size 
)
+
+ +
+
+ +

◆ mi_cfree()

+ +
+
+ + + + + + + + +
void mi_cfree (void * p)
+
+ +

Just as free but also checks if the pointer p belongs to our heap.

+ +
+
+ +

◆ mi_free_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_free_aligned (void * p,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_free_size()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void mi_free_size (void * p,
size_t size 
)
+
+ +
+
+ +

◆ mi_free_size_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void mi_free_size_aligned (void * p,
size_t size,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_malloc_size()

+ +
+
+ + + + + + + + +
size_t mi_malloc_size (const void * p)
+
+ +
+
+ +

◆ mi_malloc_usable_size()

+ +
+
+ + + + + + + + +
size_t mi_malloc_usable_size (const void * p)
+
+ +
+
+ +

◆ mi_memalign()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_memalign (size_t alignment,
size_t size 
)
+
+ +
+
+ +

◆ mi_posix_memalign()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
int mi_posix_memalign (void ** p,
size_t alignment,
size_t size 
)
+
+ +
+
+ +

◆ mi_pvalloc()

+ +
+
+ + + + + + + + +
void* mi_pvalloc (size_t size)
+
+ +
+
+ +

◆ mi_reallocarray()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_reallocarray (void * p,
size_t count,
size_t size 
)
+
+ +
+
+ +

◆ mi_valloc()

+ +
+
+ + + + + + + + +
void* mi_valloc (size_t size)
+
+ +
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/group__posix.js b/extlib/mimalloc/docs/group__posix.js new file mode 100644 index 0000000..e43453d --- /dev/null +++ b/extlib/mimalloc/docs/group__posix.js @@ -0,0 +1,16 @@ +var group__posix = +[ + [ "mi__posix_memalign", "group__posix.html#gad5a69c8fea96aa2b7a7c818c2130090a", null ], + [ "mi_aligned_alloc", "group__posix.html#ga1326d2e4388630b5f81ca7206318b8e5", null ], + [ "mi_cfree", "group__posix.html#ga705dc7a64bffacfeeb0141501a5c35d7", null ], + [ "mi_free_aligned", "group__posix.html#ga0d28d5cf61e6bfbb18c63092939fe5c9", null ], + [ "mi_free_size", "group__posix.html#gae01389eedab8d67341ff52e2aad80ebb", null ], + [ "mi_free_size_aligned", "group__posix.html#ga72e9d7ffb5fe94d69bc722c8506e27bc", null ], + [ "mi_malloc_size", "group__posix.html#ga4531c9e775bb3ae12db57c1ba8a5d7de", null ], + [ "mi_malloc_usable_size", "group__posix.html#ga06d07cf357bbac5c73ba5d0c0c421e17", null ], + [ "mi_memalign", "group__posix.html#gaab7fa71ea93b96873f5d9883db57d40e", null ], + [ "mi_posix_memalign", "group__posix.html#gacff84f226ba9feb2031b8992e5579447", null ], + [ "mi_pvalloc", "group__posix.html#gaeb325c39b887d3b90d85d1eb1712fb1e", null ], + [ "mi_reallocarray", "group__posix.html#ga48fad8648a2f1dab9c87ea9448a52088", null ], + [ "mi_valloc", "group__posix.html#ga73baaf5951f5165ba0763d0c06b6a93b", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/group__typed.html b/extlib/mimalloc/docs/group__typed.html new file mode 100644 index 0000000..5cbfbd6 --- /dev/null +++ b/extlib/mimalloc/docs/group__typed.html @@ -0,0 +1,521 @@ + + + + + + + +mi-malloc: Typed Macros + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
Typed Macros
+
+
+ +

Typed allocation macros. +More...

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Macros

#define mi_malloc_tp(tp)
 Allocate a block of type tp. More...
 
#define mi_zalloc_tp(tp)
 Allocate a zero-initialized block of type tp. More...
 
#define mi_calloc_tp(tp, count)
 Allocate count zero-initialized blocks of type tp. More...
 
#define mi_mallocn_tp(tp, count)
 Allocate count blocks of type tp. More...
 
#define mi_reallocn_tp(p, tp, count)
 Re-allocate to count blocks of type tp. More...
 
#define mi_heap_malloc_tp(hp, tp)
 Allocate a block of type tp in a heap hp. More...
 
#define mi_heap_zalloc_tp(hp, tp)
 Allocate a zero-initialized block of type tp in a heap hp. More...
 
#define mi_heap_calloc_tp(hp, tp, count)
 Allocate count zero-initialized blocks of type tp in a heap hp. More...
 
#define mi_heap_mallocn_tp(hp, tp, count)
 Allocate count blocks of type tp in a heap hp. More...
 
#define mi_heap_reallocn_tp(hp, p, tp, count)
 Re-allocate to count blocks of type tp in a heap hp. More...
 
#define mi_heap_recalloc_tp(hp, p, tp, count)
 Re-allocate to count zero initialized blocks of type tp in a heap hp. More...
 
+

Detailed Description

+

Typed allocation macros.

+

For example:

int* p = mi_malloc_tp(int)

Macro Definition Documentation

+ +

◆ mi_calloc_tp

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define mi_calloc_tp( tp,
 count 
)
+
+ +

Allocate count zero-initialized blocks of type tp.

+ +
+
+ +

◆ mi_heap_calloc_tp

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
#define mi_heap_calloc_tp( hp,
 tp,
 count 
)
+
+ +

Allocate count zero-initialized blocks of type tp in a heap hp.

+ +
+
+ +

◆ mi_heap_malloc_tp

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define mi_heap_malloc_tp( hp,
 tp 
)
+
+ +

Allocate a block of type tp in a heap hp.

+ +
+
+ +

◆ mi_heap_mallocn_tp

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
#define mi_heap_mallocn_tp( hp,
 tp,
 count 
)
+
+ +

Allocate count blocks of type tp in a heap hp.

+ +
+
+ +

◆ mi_heap_reallocn_tp

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#define mi_heap_reallocn_tp( hp,
 p,
 tp,
 count 
)
+
+ +

Re-allocate to count blocks of type tp in a heap hp.

+ +
+
+ +

◆ mi_heap_recalloc_tp

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#define mi_heap_recalloc_tp( hp,
 p,
 tp,
 count 
)
+
+ +

Re-allocate to count zero initialized blocks of type tp in a heap hp.

+ +
+
+ +

◆ mi_heap_zalloc_tp

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define mi_heap_zalloc_tp( hp,
 tp 
)
+
+ +

Allocate a zero-initialized block of type tp in a heap hp.

+ +
+
+ +

◆ mi_malloc_tp

+ +
+
+ + + + + + + + +
#define mi_malloc_tp( tp)
+
+ +

Allocate a block of type tp.

+
Parameters
+ + +
tpThe type of the block to allocate.
+
+
+
Returns
A pointer to an object of type tp, or NULL if out of memory.
+

Example:

int* p = mi_malloc_tp(int)
See also
mi_malloc()
+ +
+
+ +

◆ mi_mallocn_tp

+ +
+
+ + + + + + + + + + + + + + + + + + +
#define mi_mallocn_tp( tp,
 count 
)
+
+ +

Allocate count blocks of type tp.

+ +
+
+ +

◆ mi_reallocn_tp

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
#define mi_reallocn_tp( p,
 tp,
 count 
)
+
+ +

Re-allocate to count blocks of type tp.

+ +
+
+ +

◆ mi_zalloc_tp

+ +
+
+ + + + + + + + +
#define mi_zalloc_tp( tp)
+
+ +

Allocate a zero-initialized block of type tp.

+ +
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/group__typed.js b/extlib/mimalloc/docs/group__typed.js new file mode 100644 index 0000000..c6be55d --- /dev/null +++ b/extlib/mimalloc/docs/group__typed.js @@ -0,0 +1,14 @@ +var group__typed = +[ + [ "mi_calloc_tp", "group__typed.html#gae80c47c9d4cab10961fff1a8ac98fc07", null ], + [ "mi_heap_calloc_tp", "group__typed.html#ga4e5d1f1707c90e5f55e023ac5f45fe74", null ], + [ "mi_heap_malloc_tp", "group__typed.html#ga653bcb24ac495bc19940ecd6898f9cd7", null ], + [ "mi_heap_mallocn_tp", "group__typed.html#ga6b75cb9c4b9c647661d0924552dc6e83", null ], + [ "mi_heap_reallocn_tp", "group__typed.html#gaf213d5422ec35e7f6caad827c79bc948", null ], + [ "mi_heap_recalloc_tp", "group__typed.html#ga3e50a1600958fcaf1a7f3560c9174f9e", null ], + [ "mi_heap_zalloc_tp", "group__typed.html#gad6e87e86e994aa14416ae9b5d4c188fe", null ], + [ "mi_malloc_tp", "group__typed.html#ga0619a62c5fd886f1016030abe91f0557", null ], + [ "mi_mallocn_tp", "group__typed.html#gae5cb6e0fafc9f23169c5622e077afe8b", null ], + [ "mi_reallocn_tp", "group__typed.html#ga1158b49a55dfa81f58a4426a7578f523", null ], + [ "mi_zalloc_tp", "group__typed.html#gac77a61bdaf680a803785fe307820b48c", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/group__zeroinit.html b/extlib/mimalloc/docs/group__zeroinit.html new file mode 100644 index 0000000..3c04a5a --- /dev/null +++ b/extlib/mimalloc/docs/group__zeroinit.html @@ -0,0 +1,597 @@ + + + + + + + +mi-malloc: Zero initialized re-allocation + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+ +
+
Zero initialized re-allocation
+
+
+ +

The zero-initialized re-allocations are only valid on memory that was originally allocated with zero initialization too. +More...

+ + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

void * mi_rezalloc (void *p, size_t newsize)
 
void * mi_rezalloc_aligned (void *p, size_t newsize, size_t alignment)
 
void * mi_rezalloc_aligned_at (void *p, size_t newsize, size_t alignment, size_t offset)
 
void * mi_recalloc_aligned (void *p, size_t newcount, size_t size, size_t alignment)
 
void * mi_recalloc_aligned_at (void *p, size_t newcount, size_t size, size_t alignment, size_t offset)
 
void * mi_heap_rezalloc (mi_heap_t *heap, void *p, size_t newsize)
 
void * mi_heap_recalloc (mi_heap_t *heap, void *p, size_t newcount, size_t size)
 
void * mi_heap_rezalloc_aligned (mi_heap_t *heap, void *p, size_t newsize, size_t alignment)
 
void * mi_heap_rezalloc_aligned_at (mi_heap_t *heap, void *p, size_t newsize, size_t alignment, size_t offset)
 
void * mi_heap_recalloc_aligned (mi_heap_t *heap, void *p, size_t newcount, size_t size, size_t alignment)
 
void * mi_heap_recalloc_aligned_at (mi_heap_t *heap, void *p, size_t newcount, size_t size, size_t alignment, size_t offset)
 
+

Detailed Description

+

The zero-initialized re-allocations are only valid on memory that was originally allocated with zero initialization too.

+

e.g. mi_calloc, mi_zalloc, mi_zalloc_aligned etc. see https://github.com/microsoft/mimalloc/issues/63#issuecomment-508272992

+

Function Documentation

+ +

◆ mi_heap_recalloc()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_recalloc (mi_heap_theap,
void * p,
size_t newcount,
size_t size 
)
+
+ +
+
+ +

◆ mi_heap_recalloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_recalloc_aligned (mi_heap_theap,
void * p,
size_t newcount,
size_t size,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_heap_recalloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_recalloc_aligned_at (mi_heap_theap,
void * p,
size_t newcount,
size_t size,
size_t alignment,
size_t offset 
)
+
+ +
+
+ +

◆ mi_heap_rezalloc()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_rezalloc (mi_heap_theap,
void * p,
size_t newsize 
)
+
+ +
+
+ +

◆ mi_heap_rezalloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_rezalloc_aligned (mi_heap_theap,
void * p,
size_t newsize,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_heap_rezalloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_heap_rezalloc_aligned_at (mi_heap_theap,
void * p,
size_t newsize,
size_t alignment,
size_t offset 
)
+
+ +
+
+ +

◆ mi_recalloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_recalloc_aligned (void * p,
size_t newcount,
size_t size,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_recalloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_recalloc_aligned_at (void * p,
size_t newcount,
size_t size,
size_t alignment,
size_t offset 
)
+
+ +
+
+ +

◆ mi_rezalloc()

+ +
+
+ + + + + + + + + + + + + + + + + + +
void* mi_rezalloc (void * p,
size_t newsize 
)
+
+ +
+
+ +

◆ mi_rezalloc_aligned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_rezalloc_aligned (void * p,
size_t newsize,
size_t alignment 
)
+
+ +
+
+ +

◆ mi_rezalloc_aligned_at()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
void* mi_rezalloc_aligned_at (void * p,
size_t newsize,
size_t alignment,
size_t offset 
)
+
+ +
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/group__zeroinit.js b/extlib/mimalloc/docs/group__zeroinit.js new file mode 100644 index 0000000..b9297d2 --- /dev/null +++ b/extlib/mimalloc/docs/group__zeroinit.js @@ -0,0 +1,14 @@ +var group__zeroinit = +[ + [ "mi_heap_recalloc", "group__zeroinit.html#ga8648c5fbb22a80f0262859099f06dfbd", null ], + [ "mi_heap_recalloc_aligned", "group__zeroinit.html#ga9f3f999396c8f77ca5e80e7b40ac29e3", null ], + [ "mi_heap_recalloc_aligned_at", "group__zeroinit.html#ga496452c96f1de8c500be9fddf52edaf7", null ], + [ "mi_heap_rezalloc", "group__zeroinit.html#gacfad83f14eb5d6a42a497a898e19fc76", null ], + [ "mi_heap_rezalloc_aligned", "group__zeroinit.html#ga375fa8a611c51905e592d5d467c49664", null ], + [ "mi_heap_rezalloc_aligned_at", "group__zeroinit.html#gac90da54fa7e5d10bdc97ce0b51dce2eb", null ], + [ "mi_recalloc_aligned", "group__zeroinit.html#ga3e7e5c291acf1c7fd7ffd9914a9f945f", null ], + [ "mi_recalloc_aligned_at", "group__zeroinit.html#ga4ff5e92ad73585418a072c9d059e5cf9", null ], + [ "mi_rezalloc", "group__zeroinit.html#ga8c292e142110229a2980b37ab036dbc6", null ], + [ "mi_rezalloc_aligned", "group__zeroinit.html#gacd71a7bce96aab38ae6de17af2eb2cf0", null ], + [ "mi_rezalloc_aligned_at", "group__zeroinit.html#gae8b358c417e61d5307da002702b0a8e1", null ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/index.html b/extlib/mimalloc/docs/index.html new file mode 100644 index 0000000..ce9c983 --- /dev/null +++ b/extlib/mimalloc/docs/index.html @@ -0,0 +1,146 @@ + + + + + + + +mi-malloc: Main Page + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
mi-malloc Documentation
+
+
+

This is the API documentation of the mimalloc allocator (pronounced "me-malloc") – a general purpose allocator with excellent performance characteristics. Initially developed by Daan Leijen for the run-time systems of the Koka and Lean languages.

+

It is a drop-in replacement for malloc and can be used in other programs without code changes, for example, on Unix you can use it as:

> LD_PRELOAD=/usr/bin/libmimalloc.so myprogram

Notable aspects of the design include:

+
    +
  • small and consistent: the library is less than 6k LOC using simple and consistent data structures. This makes it very suitable to integrate and adapt in other projects. For runtime systems it provides hooks for a monotonic heartbeat and deferred freeing (for bounded worst-case times with reference counting).
  • +
  • free list sharding: the big idea: instead of one big free list (per size class) we have many smaller lists per memory "page" which both reduces fragmentation and increases locality – things that are allocated close in time get allocated close in memory. (A memory "page" in mimalloc contains blocks of one size class and is usually 64KiB on a 64-bit system).
  • +
  • eager page reset: when a "page" becomes empty (with increased chance due to free list sharding) the memory is marked to the OS as unused ("reset" or "purged") reducing (real) memory pressure and fragmentation, especially in long running programs.
  • +
  • secure: mimalloc can be build in secure mode, adding guard pages, randomized allocation, encrypted free lists, etc. to protect against various heap vulnerabilities. The performance penalty is only around 3% on average over our benchmarks.
  • +
  • first-class heaps: efficiently create and use multiple heaps to allocate across different regions. A heap can be destroyed at once instead of deallocating each object separately.
  • +
  • bounded: it does not suffer from blowup [1], has bounded worst-case allocation times (wcat), bounded space overhead (~0.2% meta-data, with at most 12.5% waste in allocation sizes), and has no internal points of contention using only atomic operations.
  • +
  • fast: In our benchmarks (see below), mimalloc always outperforms all other leading allocators (jemalloc, tcmalloc, Hoard, etc), and usually uses less memory (up to 25% more in the worst case). A nice property is that it does consistently well over a wide range of benchmarks.
  • +
+

You can read more on the design of mimalloc in the technical report which also has detailed benchmark results.

+

Further information:

+ +
+
+
+ + + + diff --git a/extlib/mimalloc/docs/jquery.js b/extlib/mimalloc/docs/jquery.js new file mode 100644 index 0000000..1ee895c --- /dev/null +++ b/extlib/mimalloc/docs/jquery.js @@ -0,0 +1,87 @@ +/*! + * jQuery JavaScript Library v1.7.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Wed Mar 21 12:46:34 2012 -0700 + */ +(function(bd,L){var av=bd.document,bu=bd.navigator,bm=bd.location;var b=(function(){var bF=function(b0,b1){return new bF.fn.init(b0,b1,bD)},bU=bd.jQuery,bH=bd.$,bD,bY=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,bM=/\S/,bI=/^\s+/,bE=/\s+$/,bA=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,bN=/^[\],:{}\s]*$/,bW=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,bP=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,bJ=/(?:^|:|,)(?:\s*\[)+/g,by=/(webkit)[ \/]([\w.]+)/,bR=/(opera)(?:.*version)?[ \/]([\w.]+)/,bQ=/(msie) ([\w.]+)/,bS=/(mozilla)(?:.*? rv:([\w.]+))?/,bB=/-([a-z]|[0-9])/ig,bZ=/^-ms-/,bT=function(b0,b1){return(b1+"").toUpperCase()},bX=bu.userAgent,bV,bC,e,bL=Object.prototype.toString,bG=Object.prototype.hasOwnProperty,bz=Array.prototype.push,bK=Array.prototype.slice,bO=String.prototype.trim,bv=Array.prototype.indexOf,bx={};bF.fn=bF.prototype={constructor:bF,init:function(b0,b4,b3){var b2,b5,b1,b6;if(!b0){return this}if(b0.nodeType){this.context=this[0]=b0;this.length=1;return this}if(b0==="body"&&!b4&&av.body){this.context=av;this[0]=av.body;this.selector=b0;this.length=1;return this}if(typeof b0==="string"){if(b0.charAt(0)==="<"&&b0.charAt(b0.length-1)===">"&&b0.length>=3){b2=[null,b0,null]}else{b2=bY.exec(b0)}if(b2&&(b2[1]||!b4)){if(b2[1]){b4=b4 instanceof bF?b4[0]:b4;b6=(b4?b4.ownerDocument||b4:av);b1=bA.exec(b0);if(b1){if(bF.isPlainObject(b4)){b0=[av.createElement(b1[1])];bF.fn.attr.call(b0,b4,true)}else{b0=[b6.createElement(b1[1])]}}else{b1=bF.buildFragment([b2[1]],[b6]);b0=(b1.cacheable?bF.clone(b1.fragment):b1.fragment).childNodes}return bF.merge(this,b0)}else{b5=av.getElementById(b2[2]);if(b5&&b5.parentNode){if(b5.id!==b2[2]){return b3.find(b0)}this.length=1;this[0]=b5}this.context=av;this.selector=b0;return this}}else{if(!b4||b4.jquery){return(b4||b3).find(b0)}else{return this.constructor(b4).find(b0)}}}else{if(bF.isFunction(b0)){return b3.ready(b0)}}if(b0.selector!==L){this.selector=b0.selector;this.context=b0.context}return bF.makeArray(b0,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return bK.call(this,0)},get:function(b0){return b0==null?this.toArray():(b0<0?this[this.length+b0]:this[b0])},pushStack:function(b1,b3,b0){var b2=this.constructor();if(bF.isArray(b1)){bz.apply(b2,b1)}else{bF.merge(b2,b1)}b2.prevObject=this;b2.context=this.context;if(b3==="find"){b2.selector=this.selector+(this.selector?" ":"")+b0}else{if(b3){b2.selector=this.selector+"."+b3+"("+b0+")"}}return b2},each:function(b1,b0){return bF.each(this,b1,b0)},ready:function(b0){bF.bindReady();bC.add(b0);return this},eq:function(b0){b0=+b0;return b0===-1?this.slice(b0):this.slice(b0,b0+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(bK.apply(this,arguments),"slice",bK.call(arguments).join(","))},map:function(b0){return this.pushStack(bF.map(this,function(b2,b1){return b0.call(b2,b1,b2)}))},end:function(){return this.prevObject||this.constructor(null)},push:bz,sort:[].sort,splice:[].splice};bF.fn.init.prototype=bF.fn;bF.extend=bF.fn.extend=function(){var b9,b2,b0,b1,b6,b7,b5=arguments[0]||{},b4=1,b3=arguments.length,b8=false;if(typeof b5==="boolean"){b8=b5;b5=arguments[1]||{};b4=2}if(typeof b5!=="object"&&!bF.isFunction(b5)){b5={}}if(b3===b4){b5=this;--b4}for(;b40){return}bC.fireWith(av,[bF]);if(bF.fn.trigger){bF(av).trigger("ready").off("ready")}}},bindReady:function(){if(bC){return}bC=bF.Callbacks("once memory");if(av.readyState==="complete"){return setTimeout(bF.ready,1)}if(av.addEventListener){av.addEventListener("DOMContentLoaded",e,false);bd.addEventListener("load",bF.ready,false)}else{if(av.attachEvent){av.attachEvent("onreadystatechange",e);bd.attachEvent("onload",bF.ready);var b0=false;try{b0=bd.frameElement==null}catch(b1){}if(av.documentElement.doScroll&&b0){bw()}}}},isFunction:function(b0){return bF.type(b0)==="function"},isArray:Array.isArray||function(b0){return bF.type(b0)==="array"},isWindow:function(b0){return b0!=null&&b0==b0.window},isNumeric:function(b0){return !isNaN(parseFloat(b0))&&isFinite(b0)},type:function(b0){return b0==null?String(b0):bx[bL.call(b0)]||"object"},isPlainObject:function(b2){if(!b2||bF.type(b2)!=="object"||b2.nodeType||bF.isWindow(b2)){return false}try{if(b2.constructor&&!bG.call(b2,"constructor")&&!bG.call(b2.constructor.prototype,"isPrototypeOf")){return false}}catch(b1){return false}var b0;for(b0 in b2){}return b0===L||bG.call(b2,b0)},isEmptyObject:function(b1){for(var b0 in b1){return false}return true},error:function(b0){throw new Error(b0)},parseJSON:function(b0){if(typeof b0!=="string"||!b0){return null}b0=bF.trim(b0);if(bd.JSON&&bd.JSON.parse){return bd.JSON.parse(b0)}if(bN.test(b0.replace(bW,"@").replace(bP,"]").replace(bJ,""))){return(new Function("return "+b0))()}bF.error("Invalid JSON: "+b0)},parseXML:function(b2){if(typeof b2!=="string"||!b2){return null}var b0,b1;try{if(bd.DOMParser){b1=new DOMParser();b0=b1.parseFromString(b2,"text/xml")}else{b0=new ActiveXObject("Microsoft.XMLDOM");b0.async="false";b0.loadXML(b2)}}catch(b3){b0=L}if(!b0||!b0.documentElement||b0.getElementsByTagName("parsererror").length){bF.error("Invalid XML: "+b2)}return b0},noop:function(){},globalEval:function(b0){if(b0&&bM.test(b0)){(bd.execScript||function(b1){bd["eval"].call(bd,b1)})(b0)}},camelCase:function(b0){return b0.replace(bZ,"ms-").replace(bB,bT)},nodeName:function(b1,b0){return b1.nodeName&&b1.nodeName.toUpperCase()===b0.toUpperCase()},each:function(b3,b6,b2){var b1,b4=0,b5=b3.length,b0=b5===L||bF.isFunction(b3);if(b2){if(b0){for(b1 in b3){if(b6.apply(b3[b1],b2)===false){break}}}else{for(;b40&&b0[0]&&b0[b1-1])||b1===0||bF.isArray(b0));if(b3){for(;b21?aK.call(arguments,0):bG;if(!(--bw)){bC.resolveWith(bC,bx)}}}function bz(bF){return function(bG){bB[bF]=arguments.length>1?aK.call(arguments,0):bG;bC.notifyWith(bE,bB)}}if(e>1){for(;bv
a";bH=bv.getElementsByTagName("*");bE=bv.getElementsByTagName("a")[0];if(!bH||!bH.length||!bE){return{}}bF=av.createElement("select");bx=bF.appendChild(av.createElement("option"));bD=bv.getElementsByTagName("input")[0];bI={leadingWhitespace:(bv.firstChild.nodeType===3),tbody:!bv.getElementsByTagName("tbody").length,htmlSerialize:!!bv.getElementsByTagName("link").length,style:/top/.test(bE.getAttribute("style")),hrefNormalized:(bE.getAttribute("href")==="/a"),opacity:/^0.55/.test(bE.style.opacity),cssFloat:!!bE.style.cssFloat,checkOn:(bD.value==="on"),optSelected:bx.selected,getSetAttribute:bv.className!=="t",enctype:!!av.createElement("form").enctype,html5Clone:av.createElement("nav").cloneNode(true).outerHTML!=="<:nav>",submitBubbles:true,changeBubbles:true,focusinBubbles:false,deleteExpando:true,noCloneEvent:true,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableMarginRight:true,pixelMargin:true};b.boxModel=bI.boxModel=(av.compatMode==="CSS1Compat");bD.checked=true;bI.noCloneChecked=bD.cloneNode(true).checked;bF.disabled=true;bI.optDisabled=!bx.disabled;try{delete bv.test}catch(bB){bI.deleteExpando=false}if(!bv.addEventListener&&bv.attachEvent&&bv.fireEvent){bv.attachEvent("onclick",function(){bI.noCloneEvent=false});bv.cloneNode(true).fireEvent("onclick")}bD=av.createElement("input");bD.value="t";bD.setAttribute("type","radio");bI.radioValue=bD.value==="t";bD.setAttribute("checked","checked");bD.setAttribute("name","t");bv.appendChild(bD);bC=av.createDocumentFragment();bC.appendChild(bv.lastChild);bI.checkClone=bC.cloneNode(true).cloneNode(true).lastChild.checked;bI.appendChecked=bD.checked;bC.removeChild(bD);bC.appendChild(bv);if(bv.attachEvent){for(by in {submit:1,change:1,focusin:1}){bA="on"+by;bw=(bA in bv);if(!bw){bv.setAttribute(bA,"return;");bw=(typeof bv[bA]==="function")}bI[by+"Bubbles"]=bw}}bC.removeChild(bv);bC=bF=bx=bv=bD=null;b(function(){var bM,bV,bW,bU,bO,bP,bR,bL,bK,bQ,bN,e,bT,bS=av.getElementsByTagName("body")[0];if(!bS){return}bL=1;bT="padding:0;margin:0;border:";bN="position:absolute;top:0;left:0;width:1px;height:1px;";e=bT+"0;visibility:hidden;";bK="style='"+bN+bT+"5px solid #000;";bQ="
";bM=av.createElement("div");bM.style.cssText=e+"width:0;height:0;position:static;top:0;margin-top:"+bL+"px";bS.insertBefore(bM,bS.firstChild);bv=av.createElement("div");bM.appendChild(bv);bv.innerHTML="
t
";bz=bv.getElementsByTagName("td");bw=(bz[0].offsetHeight===0);bz[0].style.display="";bz[1].style.display="none";bI.reliableHiddenOffsets=bw&&(bz[0].offsetHeight===0);if(bd.getComputedStyle){bv.innerHTML="";bR=av.createElement("div");bR.style.width="0";bR.style.marginRight="0";bv.style.width="2px";bv.appendChild(bR);bI.reliableMarginRight=(parseInt((bd.getComputedStyle(bR,null)||{marginRight:0}).marginRight,10)||0)===0}if(typeof bv.style.zoom!=="undefined"){bv.innerHTML="";bv.style.width=bv.style.padding="1px";bv.style.border=0;bv.style.overflow="hidden";bv.style.display="inline";bv.style.zoom=1;bI.inlineBlockNeedsLayout=(bv.offsetWidth===3);bv.style.display="block";bv.style.overflow="visible";bv.innerHTML="
";bI.shrinkWrapBlocks=(bv.offsetWidth!==3)}bv.style.cssText=bN+e;bv.innerHTML=bQ;bV=bv.firstChild;bW=bV.firstChild;bO=bV.nextSibling.firstChild.firstChild;bP={doesNotAddBorder:(bW.offsetTop!==5),doesAddBorderForTableAndCells:(bO.offsetTop===5)};bW.style.position="fixed";bW.style.top="20px";bP.fixedPosition=(bW.offsetTop===20||bW.offsetTop===15);bW.style.position=bW.style.top="";bV.style.overflow="hidden";bV.style.position="relative";bP.subtractsBorderForOverflowNotVisible=(bW.offsetTop===-5);bP.doesNotIncludeMarginInBodyOffset=(bS.offsetTop!==bL);if(bd.getComputedStyle){bv.style.marginTop="1%";bI.pixelMargin=(bd.getComputedStyle(bv,null)||{marginTop:0}).marginTop!=="1%"}if(typeof bM.style.zoom!=="undefined"){bM.style.zoom=1}bS.removeChild(bM);bR=bv=bM=null;b.extend(bI,bP)});return bI})();var aT=/^(?:\{.*\}|\[.*\])$/,aA=/([A-Z])/g;b.extend({cache:{},uuid:0,expando:"jQuery"+(b.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},hasData:function(e){e=e.nodeType?b.cache[e[b.expando]]:e[b.expando];return !!e&&!S(e)},data:function(bx,bv,bz,by){if(!b.acceptData(bx)){return}var bG,bA,bD,bE=b.expando,bC=typeof bv==="string",bF=bx.nodeType,e=bF?b.cache:bx,bw=bF?bx[bE]:bx[bE]&&bE,bB=bv==="events";if((!bw||!e[bw]||(!bB&&!by&&!e[bw].data))&&bC&&bz===L){return}if(!bw){if(bF){bx[bE]=bw=++b.uuid}else{bw=bE}}if(!e[bw]){e[bw]={};if(!bF){e[bw].toJSON=b.noop}}if(typeof bv==="object"||typeof bv==="function"){if(by){e[bw]=b.extend(e[bw],bv)}else{e[bw].data=b.extend(e[bw].data,bv)}}bG=bA=e[bw];if(!by){if(!bA.data){bA.data={}}bA=bA.data}if(bz!==L){bA[b.camelCase(bv)]=bz}if(bB&&!bA[bv]){return bG.events}if(bC){bD=bA[bv];if(bD==null){bD=bA[b.camelCase(bv)]}}else{bD=bA}return bD},removeData:function(bx,bv,by){if(!b.acceptData(bx)){return}var bB,bA,bz,bC=b.expando,bD=bx.nodeType,e=bD?b.cache:bx,bw=bD?bx[bC]:bC;if(!e[bw]){return}if(bv){bB=by?e[bw]:e[bw].data;if(bB){if(!b.isArray(bv)){if(bv in bB){bv=[bv]}else{bv=b.camelCase(bv);if(bv in bB){bv=[bv]}else{bv=bv.split(" ")}}}for(bA=0,bz=bv.length;bA1,null,false)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function a6(bx,bw,by){if(by===L&&bx.nodeType===1){var bv="data-"+bw.replace(aA,"-$1").toLowerCase();by=bx.getAttribute(bv);if(typeof by==="string"){try{by=by==="true"?true:by==="false"?false:by==="null"?null:b.isNumeric(by)?+by:aT.test(by)?b.parseJSON(by):by}catch(bz){}b.data(bx,bw,by)}else{by=L}}return by}function S(bv){for(var e in bv){if(e==="data"&&b.isEmptyObject(bv[e])){continue}if(e!=="toJSON"){return false}}return true}function bj(by,bx,bA){var bw=bx+"defer",bv=bx+"queue",e=bx+"mark",bz=b._data(by,bw);if(bz&&(bA==="queue"||!b._data(by,bv))&&(bA==="mark"||!b._data(by,e))){setTimeout(function(){if(!b._data(by,bv)&&!b._data(by,e)){b.removeData(by,bw,true);bz.fire()}},0)}}b.extend({_mark:function(bv,e){if(bv){e=(e||"fx")+"mark";b._data(bv,e,(b._data(bv,e)||0)+1)}},_unmark:function(by,bx,bv){if(by!==true){bv=bx;bx=by;by=false}if(bx){bv=bv||"fx";var e=bv+"mark",bw=by?0:((b._data(bx,e)||1)-1);if(bw){b._data(bx,e,bw)}else{b.removeData(bx,e,true);bj(bx,bv,"mark")}}},queue:function(bv,e,bx){var bw;if(bv){e=(e||"fx")+"queue";bw=b._data(bv,e);if(bx){if(!bw||b.isArray(bx)){bw=b._data(bv,e,b.makeArray(bx))}else{bw.push(bx)}}return bw||[]}},dequeue:function(by,bx){bx=bx||"fx";var bv=b.queue(by,bx),bw=bv.shift(),e={};if(bw==="inprogress"){bw=bv.shift()}if(bw){if(bx==="fx"){bv.unshift("inprogress")}b._data(by,bx+".run",e);bw.call(by,function(){b.dequeue(by,bx)},e)}if(!bv.length){b.removeData(by,bx+"queue "+bx+".run",true);bj(by,bx,"queue")}}});b.fn.extend({queue:function(e,bv){var bw=2;if(typeof e!=="string"){bv=e;e="fx";bw--}if(arguments.length1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,bv){return b.access(this,b.prop,e,bv,arguments.length>1)},removeProp:function(e){e=b.propFix[e]||e;return this.each(function(){try{this[e]=L;delete this[e]}catch(bv){}})},addClass:function(by){var bA,bw,bv,bx,bz,bB,e;if(b.isFunction(by)){return this.each(function(bC){b(this).addClass(by.call(this,bC,this.className))})}if(by&&typeof by==="string"){bA=by.split(ag);for(bw=0,bv=this.length;bw-1){return true}}return false},val:function(bx){var e,bv,by,bw=this[0];if(!arguments.length){if(bw){e=b.valHooks[bw.type]||b.valHooks[bw.nodeName.toLowerCase()];if(e&&"get" in e&&(bv=e.get(bw,"value"))!==L){return bv}bv=bw.value;return typeof bv==="string"?bv.replace(aV,""):bv==null?"":bv}return}by=b.isFunction(bx);return this.each(function(bA){var bz=b(this),bB;if(this.nodeType!==1){return}if(by){bB=bx.call(this,bA,bz.val())}else{bB=bx}if(bB==null){bB=""}else{if(typeof bB==="number"){bB+=""}else{if(b.isArray(bB)){bB=b.map(bB,function(bC){return bC==null?"":bC+""})}}}e=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()];if(!e||!("set" in e)||e.set(this,bB,"value")===L){this.value=bB}})}});b.extend({valHooks:{option:{get:function(e){var bv=e.attributes.value;return !bv||bv.specified?e.value:e.text}},select:{get:function(e){var bA,bv,bz,bx,by=e.selectedIndex,bB=[],bC=e.options,bw=e.type==="select-one";if(by<0){return null}bv=bw?by:0;bz=bw?by+1:bC.length;for(;bv=0});if(!e.length){bv.selectedIndex=-1}return e}}},attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(bA,bx,bB,bz){var bw,e,by,bv=bA.nodeType;if(!bA||bv===3||bv===8||bv===2){return}if(bz&&bx in b.attrFn){return b(bA)[bx](bB)}if(typeof bA.getAttribute==="undefined"){return b.prop(bA,bx,bB)}by=bv!==1||!b.isXMLDoc(bA);if(by){bx=bx.toLowerCase();e=b.attrHooks[bx]||(ao.test(bx)?aZ:bf)}if(bB!==L){if(bB===null){b.removeAttr(bA,bx);return}else{if(e&&"set" in e&&by&&(bw=e.set(bA,bB,bx))!==L){return bw}else{bA.setAttribute(bx,""+bB);return bB}}}else{if(e&&"get" in e&&by&&(bw=e.get(bA,bx))!==null){return bw}else{bw=bA.getAttribute(bx);return bw===null?L:bw}}},removeAttr:function(by,bA){var bz,bB,bw,e,bv,bx=0;if(bA&&by.nodeType===1){bB=bA.toLowerCase().split(ag);e=bB.length;for(;bx=0)}}})});var be=/^(?:textarea|input|select)$/i,n=/^([^\.]*)?(?:\.(.+))?$/,J=/(?:^|\s)hover(\.\S+)?\b/,aP=/^key/,bg=/^(?:mouse|contextmenu)|click/,T=/^(?:focusinfocus|focusoutblur)$/,U=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,Y=function(e){var bv=U.exec(e);if(bv){bv[1]=(bv[1]||"").toLowerCase();bv[3]=bv[3]&&new RegExp("(?:^|\\s)"+bv[3]+"(?:\\s|$)")}return bv},j=function(bw,e){var bv=bw.attributes||{};return((!e[1]||bw.nodeName.toLowerCase()===e[1])&&(!e[2]||(bv.id||{}).value===e[2])&&(!e[3]||e[3].test((bv["class"]||{}).value)))},bt=function(e){return b.event.special.hover?e:e.replace(J,"mouseenter$1 mouseleave$1")};b.event={add:function(bx,bC,bJ,bA,by){var bD,bB,bK,bI,bH,bF,e,bG,bv,bz,bw,bE;if(bx.nodeType===3||bx.nodeType===8||!bC||!bJ||!(bD=b._data(bx))){return}if(bJ.handler){bv=bJ;bJ=bv.handler;by=bv.selector}if(!bJ.guid){bJ.guid=b.guid++}bK=bD.events;if(!bK){bD.events=bK={}}bB=bD.handle;if(!bB){bD.handle=bB=function(bL){return typeof b!=="undefined"&&(!bL||b.event.triggered!==bL.type)?b.event.dispatch.apply(bB.elem,arguments):L};bB.elem=bx}bC=b.trim(bt(bC)).split(" ");for(bI=0;bI=0){bG=bG.slice(0,-1);bw=true}if(bG.indexOf(".")>=0){bx=bG.split(".");bG=bx.shift();bx.sort()}if((!bA||b.event.customEvent[bG])&&!b.event.global[bG]){return}bv=typeof bv==="object"?bv[b.expando]?bv:new b.Event(bG,bv):new b.Event(bG);bv.type=bG;bv.isTrigger=true;bv.exclusive=bw;bv.namespace=bx.join(".");bv.namespace_re=bv.namespace?new RegExp("(^|\\.)"+bx.join("\\.(?:.*\\.)?")+"(\\.|$)"):null;by=bG.indexOf(":")<0?"on"+bG:"";if(!bA){e=b.cache;for(bC in e){if(e[bC].events&&e[bC].events[bG]){b.event.trigger(bv,bD,e[bC].handle.elem,true)}}return}bv.result=L;if(!bv.target){bv.target=bA}bD=bD!=null?b.makeArray(bD):[];bD.unshift(bv);bF=b.event.special[bG]||{};if(bF.trigger&&bF.trigger.apply(bA,bD)===false){return}bB=[[bA,bF.bindType||bG]];if(!bJ&&!bF.noBubble&&!b.isWindow(bA)){bI=bF.delegateType||bG;bH=T.test(bI+bG)?bA:bA.parentNode;bz=null;for(;bH;bH=bH.parentNode){bB.push([bH,bI]);bz=bH}if(bz&&bz===bA.ownerDocument){bB.push([bz.defaultView||bz.parentWindow||bd,bI])}}for(bC=0;bCbC){bv.push({elem:this,matches:bD.slice(bC)})}for(bJ=0;bJ0?this.on(e,null,bx,bw):this.trigger(e)};if(b.attrFn){b.attrFn[e]=true}if(aP.test(e)){b.event.fixHooks[e]=b.event.keyHooks}if(bg.test(e)){b.event.fixHooks[e]=b.event.mouseHooks}}); +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var bH=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,bC="sizcache"+(Math.random()+"").replace(".",""),bI=0,bL=Object.prototype.toString,bB=false,bA=true,bK=/\\/g,bO=/\r\n/g,bQ=/\W/;[0,0].sort(function(){bA=false;return 0});var by=function(bV,e,bY,bZ){bY=bY||[];e=e||av;var b1=e;if(e.nodeType!==1&&e.nodeType!==9){return[]}if(!bV||typeof bV!=="string"){return bY}var bS,b3,b6,bR,b2,b5,b4,bX,bU=true,bT=by.isXML(e),bW=[],b0=bV;do{bH.exec("");bS=bH.exec(b0);if(bS){b0=bS[3];bW.push(bS[1]);if(bS[2]){bR=bS[3];break}}}while(bS);if(bW.length>1&&bD.exec(bV)){if(bW.length===2&&bE.relative[bW[0]]){b3=bM(bW[0]+bW[1],e,bZ)}else{b3=bE.relative[bW[0]]?[e]:by(bW.shift(),e);while(bW.length){bV=bW.shift();if(bE.relative[bV]){bV+=bW.shift()}b3=bM(bV,b3,bZ)}}}else{if(!bZ&&bW.length>1&&e.nodeType===9&&!bT&&bE.match.ID.test(bW[0])&&!bE.match.ID.test(bW[bW.length-1])){b2=by.find(bW.shift(),e,bT);e=b2.expr?by.filter(b2.expr,b2.set)[0]:b2.set[0]}if(e){b2=bZ?{expr:bW.pop(),set:bF(bZ)}:by.find(bW.pop(),bW.length===1&&(bW[0]==="~"||bW[0]==="+")&&e.parentNode?e.parentNode:e,bT);b3=b2.expr?by.filter(b2.expr,b2.set):b2.set;if(bW.length>0){b6=bF(b3)}else{bU=false}while(bW.length){b5=bW.pop();b4=b5;if(!bE.relative[b5]){b5=""}else{b4=bW.pop()}if(b4==null){b4=e}bE.relative[b5](b6,b4,bT)}}else{b6=bW=[]}}if(!b6){b6=b3}if(!b6){by.error(b5||bV)}if(bL.call(b6)==="[object Array]"){if(!bU){bY.push.apply(bY,b6)}else{if(e&&e.nodeType===1){for(bX=0;b6[bX]!=null;bX++){if(b6[bX]&&(b6[bX]===true||b6[bX].nodeType===1&&by.contains(e,b6[bX]))){bY.push(b3[bX])}}}else{for(bX=0;b6[bX]!=null;bX++){if(b6[bX]&&b6[bX].nodeType===1){bY.push(b3[bX])}}}}}else{bF(b6,bY)}if(bR){by(bR,b1,bY,bZ);by.uniqueSort(bY)}return bY};by.uniqueSort=function(bR){if(bJ){bB=bA;bR.sort(bJ);if(bB){for(var e=1;e0};by.find=function(bX,e,bY){var bW,bS,bU,bT,bV,bR;if(!bX){return[]}for(bS=0,bU=bE.order.length;bS":function(bW,bR){var bV,bU=typeof bR==="string",bS=0,e=bW.length;if(bU&&!bQ.test(bR)){bR=bR.toLowerCase();for(;bS=0)){if(!bS){e.push(bV)}}else{if(bS){bR[bU]=false}}}}return false},ID:function(e){return e[1].replace(bK,"")},TAG:function(bR,e){return bR[1].replace(bK,"").toLowerCase()},CHILD:function(e){if(e[1]==="nth"){if(!e[2]){by.error(e[0])}e[2]=e[2].replace(/^\+|\s*/g,"");var bR=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(e[2]==="even"&&"2n"||e[2]==="odd"&&"2n+1"||!/\D/.test(e[2])&&"0n+"+e[2]||e[2]);e[2]=(bR[1]+(bR[2]||1))-0;e[3]=bR[3]-0}else{if(e[2]){by.error(e[0])}}e[0]=bI++;return e},ATTR:function(bU,bR,bS,e,bV,bW){var bT=bU[1]=bU[1].replace(bK,"");if(!bW&&bE.attrMap[bT]){bU[1]=bE.attrMap[bT]}bU[4]=(bU[4]||bU[5]||"").replace(bK,"");if(bU[2]==="~="){bU[4]=" "+bU[4]+" "}return bU},PSEUDO:function(bU,bR,bS,e,bV){if(bU[1]==="not"){if((bH.exec(bU[3])||"").length>1||/^\w/.test(bU[3])){bU[3]=by(bU[3],null,null,bR)}else{var bT=by.filter(bU[3],bR,bS,true^bV);if(!bS){e.push.apply(e,bT)}return false}}else{if(bE.match.POS.test(bU[0])||bE.match.CHILD.test(bU[0])){return true}}return bU},POS:function(e){e.unshift(true);return e}},filters:{enabled:function(e){return e.disabled===false&&e.type!=="hidden"},disabled:function(e){return e.disabled===true},checked:function(e){return e.checked===true},selected:function(e){if(e.parentNode){e.parentNode.selectedIndex}return e.selected===true},parent:function(e){return !!e.firstChild},empty:function(e){return !e.firstChild},has:function(bS,bR,e){return !!by(e[3],bS).length},header:function(e){return(/h\d/i).test(e.nodeName)},text:function(bS){var e=bS.getAttribute("type"),bR=bS.type;return bS.nodeName.toLowerCase()==="input"&&"text"===bR&&(e===bR||e===null)},radio:function(e){return e.nodeName.toLowerCase()==="input"&&"radio"===e.type},checkbox:function(e){return e.nodeName.toLowerCase()==="input"&&"checkbox"===e.type},file:function(e){return e.nodeName.toLowerCase()==="input"&&"file"===e.type},password:function(e){return e.nodeName.toLowerCase()==="input"&&"password"===e.type},submit:function(bR){var e=bR.nodeName.toLowerCase();return(e==="input"||e==="button")&&"submit"===bR.type},image:function(e){return e.nodeName.toLowerCase()==="input"&&"image"===e.type},reset:function(bR){var e=bR.nodeName.toLowerCase();return(e==="input"||e==="button")&&"reset"===bR.type},button:function(bR){var e=bR.nodeName.toLowerCase();return e==="input"&&"button"===bR.type||e==="button"},input:function(e){return(/input|select|textarea|button/i).test(e.nodeName)},focus:function(e){return e===e.ownerDocument.activeElement}},setFilters:{first:function(bR,e){return e===0},last:function(bS,bR,e,bT){return bR===bT.length-1},even:function(bR,e){return e%2===0},odd:function(bR,e){return e%2===1},lt:function(bS,bR,e){return bRe[3]-0},nth:function(bS,bR,e){return e[3]-0===bR},eq:function(bS,bR,e){return e[3]-0===bR}},filter:{PSEUDO:function(bS,bX,bW,bY){var e=bX[1],bR=bE.filters[e];if(bR){return bR(bS,bW,bX,bY)}else{if(e==="contains"){return(bS.textContent||bS.innerText||bw([bS])||"").indexOf(bX[3])>=0}else{if(e==="not"){var bT=bX[3];for(var bV=0,bU=bT.length;bV=0)}}},ID:function(bR,e){return bR.nodeType===1&&bR.getAttribute("id")===e},TAG:function(bR,e){return(e==="*"&&bR.nodeType===1)||!!bR.nodeName&&bR.nodeName.toLowerCase()===e},CLASS:function(bR,e){return(" "+(bR.className||bR.getAttribute("class"))+" ").indexOf(e)>-1},ATTR:function(bV,bT){var bS=bT[1],e=by.attr?by.attr(bV,bS):bE.attrHandle[bS]?bE.attrHandle[bS](bV):bV[bS]!=null?bV[bS]:bV.getAttribute(bS),bW=e+"",bU=bT[2],bR=bT[4];return e==null?bU==="!=":!bU&&by.attr?e!=null:bU==="="?bW===bR:bU==="*="?bW.indexOf(bR)>=0:bU==="~="?(" "+bW+" ").indexOf(bR)>=0:!bR?bW&&e!==false:bU==="!="?bW!==bR:bU==="^="?bW.indexOf(bR)===0:bU==="$="?bW.substr(bW.length-bR.length)===bR:bU==="|="?bW===bR||bW.substr(0,bR.length+1)===bR+"-":false},POS:function(bU,bR,bS,bV){var e=bR[2],bT=bE.setFilters[e];if(bT){return bT(bU,bS,bR,bV)}}}};var bD=bE.match.POS,bx=function(bR,e){return"\\"+(e-0+1)};for(var bz in bE.match){bE.match[bz]=new RegExp(bE.match[bz].source+(/(?![^\[]*\])(?![^\(]*\))/.source));bE.leftMatch[bz]=new RegExp(/(^(?:.|\r|\n)*?)/.source+bE.match[bz].source.replace(/\\(\d+)/g,bx))}bE.match.globalPOS=bD;var bF=function(bR,e){bR=Array.prototype.slice.call(bR,0);if(e){e.push.apply(e,bR);return e}return bR};try{Array.prototype.slice.call(av.documentElement.childNodes,0)[0].nodeType}catch(bP){bF=function(bU,bT){var bS=0,bR=bT||[];if(bL.call(bU)==="[object Array]"){Array.prototype.push.apply(bR,bU)}else{if(typeof bU.length==="number"){for(var e=bU.length;bS";e.insertBefore(bR,e.firstChild);if(av.getElementById(bS)){bE.find.ID=function(bU,bV,bW){if(typeof bV.getElementById!=="undefined"&&!bW){var bT=bV.getElementById(bU[1]);return bT?bT.id===bU[1]||typeof bT.getAttributeNode!=="undefined"&&bT.getAttributeNode("id").nodeValue===bU[1]?[bT]:L:[]}};bE.filter.ID=function(bV,bT){var bU=typeof bV.getAttributeNode!=="undefined"&&bV.getAttributeNode("id");return bV.nodeType===1&&bU&&bU.nodeValue===bT}}e.removeChild(bR);e=bR=null})();(function(){var e=av.createElement("div");e.appendChild(av.createComment(""));if(e.getElementsByTagName("*").length>0){bE.find.TAG=function(bR,bV){var bU=bV.getElementsByTagName(bR[1]);if(bR[1]==="*"){var bT=[];for(var bS=0;bU[bS];bS++){if(bU[bS].nodeType===1){bT.push(bU[bS])}}bU=bT}return bU}}e.innerHTML="";if(e.firstChild&&typeof e.firstChild.getAttribute!=="undefined"&&e.firstChild.getAttribute("href")!=="#"){bE.attrHandle.href=function(bR){return bR.getAttribute("href",2)}}e=null})();if(av.querySelectorAll){(function(){var e=by,bT=av.createElement("div"),bS="__sizzle__";bT.innerHTML="

";if(bT.querySelectorAll&&bT.querySelectorAll(".TEST").length===0){return}by=function(b4,bV,bZ,b3){bV=bV||av;if(!b3&&!by.isXML(bV)){var b2=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b4);if(b2&&(bV.nodeType===1||bV.nodeType===9)){if(b2[1]){return bF(bV.getElementsByTagName(b4),bZ)}else{if(b2[2]&&bE.find.CLASS&&bV.getElementsByClassName){return bF(bV.getElementsByClassName(b2[2]),bZ)}}}if(bV.nodeType===9){if(b4==="body"&&bV.body){return bF([bV.body],bZ)}else{if(b2&&b2[3]){var bY=bV.getElementById(b2[3]);if(bY&&bY.parentNode){if(bY.id===b2[3]){return bF([bY],bZ)}}else{return bF([],bZ)}}}try{return bF(bV.querySelectorAll(b4),bZ)}catch(b0){}}else{if(bV.nodeType===1&&bV.nodeName.toLowerCase()!=="object"){var bW=bV,bX=bV.getAttribute("id"),bU=bX||bS,b6=bV.parentNode,b5=/^\s*[+~]/.test(b4);if(!bX){bV.setAttribute("id",bU)}else{bU=bU.replace(/'/g,"\\$&")}if(b5&&b6){bV=bV.parentNode}try{if(!b5||b6){return bF(bV.querySelectorAll("[id='"+bU+"'] "+b4),bZ)}}catch(b1){}finally{if(!bX){bW.removeAttribute("id")}}}}}return e(b4,bV,bZ,b3)};for(var bR in e){by[bR]=e[bR]}bT=null})()}(function(){var e=av.documentElement,bS=e.matchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.msMatchesSelector;if(bS){var bU=!bS.call(av.createElement("div"),"div"),bR=false;try{bS.call(av.documentElement,"[test!='']:sizzle")}catch(bT){bR=true}by.matchesSelector=function(bW,bY){bY=bY.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!by.isXML(bW)){try{if(bR||!bE.match.PSEUDO.test(bY)&&!/!=/.test(bY)){var bV=bS.call(bW,bY);if(bV||!bU||bW.document&&bW.document.nodeType!==11){return bV}}}catch(bX){}}return by(bY,null,null,[bW]).length>0}}})();(function(){var e=av.createElement("div");e.innerHTML="
";if(!e.getElementsByClassName||e.getElementsByClassName("e").length===0){return}e.lastChild.className="e";if(e.getElementsByClassName("e").length===1){return}bE.order.splice(1,0,"CLASS");bE.find.CLASS=function(bR,bS,bT){if(typeof bS.getElementsByClassName!=="undefined"&&!bT){return bS.getElementsByClassName(bR[1])}};e=null})();function bv(bR,bW,bV,bZ,bX,bY){for(var bT=0,bS=bZ.length;bT0){bU=e;break}}}e=e[bR]}bZ[bT]=bU}}}if(av.documentElement.contains){by.contains=function(bR,e){return bR!==e&&(bR.contains?bR.contains(e):true)}}else{if(av.documentElement.compareDocumentPosition){by.contains=function(bR,e){return !!(bR.compareDocumentPosition(e)&16)}}else{by.contains=function(){return false}}}by.isXML=function(e){var bR=(e?e.ownerDocument||e:0).documentElement;return bR?bR.nodeName!=="HTML":false};var bM=function(bS,e,bW){var bV,bX=[],bU="",bY=e.nodeType?[e]:e;while((bV=bE.match.PSEUDO.exec(bS))){bU+=bV[0];bS=bS.replace(bE.match.PSEUDO,"")}bS=bE.relative[bS]?bS+"*":bS;for(var bT=0,bR=bY.length;bT0){for(bB=bA;bB=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(by,bx){var bv=[],bw,e,bz=this[0];if(b.isArray(by)){var bB=1;while(bz&&bz.ownerDocument&&bz!==bx){for(bw=0;bw-1:b.find.matchesSelector(bz,by)){bv.push(bz);break}else{bz=bz.parentNode;if(!bz||!bz.ownerDocument||bz===bx||bz.nodeType===11){break}}}}bv=bv.length>1?b.unique(bv):bv;return this.pushStack(bv,"closest",by)},index:function(e){if(!e){return(this[0]&&this[0].parentNode)?this.prevAll().length:-1}if(typeof e==="string"){return b.inArray(this[0],b(e))}return b.inArray(e.jquery?e[0]:e,this)},add:function(e,bv){var bx=typeof e==="string"?b(e,bv):b.makeArray(e&&e.nodeType?[e]:e),bw=b.merge(this.get(),bx);return this.pushStack(B(bx[0])||B(bw[0])?bw:b.unique(bw))},andSelf:function(){return this.add(this.prevObject)}});function B(e){return !e||!e.parentNode||e.parentNode.nodeType===11}b.each({parent:function(bv){var e=bv.parentNode;return e&&e.nodeType!==11?e:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(bv,e,bw){return b.dir(bv,"parentNode",bw)},next:function(e){return b.nth(e,2,"nextSibling")},prev:function(e){return b.nth(e,2,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(bv,e,bw){return b.dir(bv,"nextSibling",bw)},prevUntil:function(bv,e,bw){return b.dir(bv,"previousSibling",bw)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.makeArray(e.childNodes)}},function(e,bv){b.fn[e]=function(by,bw){var bx=b.map(this,bv,by);if(!ab.test(e)){bw=by}if(bw&&typeof bw==="string"){bx=b.filter(bw,bx)}bx=this.length>1&&!ay[e]?b.unique(bx):bx;if((this.length>1||bb.test(bw))&&aq.test(e)){bx=bx.reverse()}return this.pushStack(bx,e,P.call(arguments).join(","))}});b.extend({filter:function(bw,e,bv){if(bv){bw=":not("+bw+")"}return e.length===1?b.find.matchesSelector(e[0],bw)?[e[0]]:[]:b.find.matches(bw,e)},dir:function(bw,bv,by){var e=[],bx=bw[bv];while(bx&&bx.nodeType!==9&&(by===L||bx.nodeType!==1||!b(bx).is(by))){if(bx.nodeType===1){e.push(bx)}bx=bx[bv]}return e},nth:function(by,e,bw,bx){e=e||1;var bv=0;for(;by;by=by[bw]){if(by.nodeType===1&&++bv===e){break}}return by},sibling:function(bw,bv){var e=[];for(;bw;bw=bw.nextSibling){if(bw.nodeType===1&&bw!==bv){e.push(bw)}}return e}});function aH(bx,bw,e){bw=bw||0;if(b.isFunction(bw)){return b.grep(bx,function(bz,by){var bA=!!bw.call(bz,by,bz);return bA===e})}else{if(bw.nodeType){return b.grep(bx,function(bz,by){return(bz===bw)===e})}else{if(typeof bw==="string"){var bv=b.grep(bx,function(by){return by.nodeType===1});if(bp.test(bw)){return b.filter(bw,bv,!e)}else{bw=b.filter(bw,bv)}}}}return b.grep(bx,function(bz,by){return(b.inArray(bz,bw)>=0)===e})}function a(e){var bw=aS.split("|"),bv=e.createDocumentFragment();if(bv.createElement){while(bw.length){bv.createElement(bw.pop())}}return bv}var aS="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ah=/ jQuery\d+="(?:\d+|null)"/g,ar=/^\s+/,R=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,d=/<([\w:]+)/,v=/]","i"),o=/checked\s*(?:[^=]|=\s*.checked.)/i,bn=/\/(java|ecma)script/i,aO=/^\s*",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},ac=a(av);ax.optgroup=ax.option;ax.tbody=ax.tfoot=ax.colgroup=ax.caption=ax.thead;ax.th=ax.td;if(!b.support.htmlSerialize){ax._default=[1,"div
","
"]}b.fn.extend({text:function(e){return b.access(this,function(bv){return bv===L?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||av).createTextNode(bv))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e)){return this.each(function(bw){b(this).wrapAll(e.call(this,bw))})}if(this[0]){var bv=b(e,this[0].ownerDocument).eq(0).clone(true);if(this[0].parentNode){bv.insertBefore(this[0])}bv.map(function(){var bw=this;while(bw.firstChild&&bw.firstChild.nodeType===1){bw=bw.firstChild}return bw}).append(this)}return this},wrapInner:function(e){if(b.isFunction(e)){return this.each(function(bv){b(this).wrapInner(e.call(this,bv))})}return this.each(function(){var bv=b(this),bw=bv.contents();if(bw.length){bw.wrapAll(e)}else{bv.append(e)}})},wrap:function(e){var bv=b.isFunction(e);return this.each(function(bw){b(this).wrapAll(bv?e.call(this,bw):e)})},unwrap:function(){return this.parent().each(function(){if(!b.nodeName(this,"body")){b(this).replaceWith(this.childNodes)}}).end()},append:function(){return this.domManip(arguments,true,function(e){if(this.nodeType===1){this.appendChild(e)}})},prepend:function(){return this.domManip(arguments,true,function(e){if(this.nodeType===1){this.insertBefore(e,this.firstChild)}})},before:function(){if(this[0]&&this[0].parentNode){return this.domManip(arguments,false,function(bv){this.parentNode.insertBefore(bv,this)})}else{if(arguments.length){var e=b.clean(arguments);e.push.apply(e,this.toArray());return this.pushStack(e,"before",arguments)}}},after:function(){if(this[0]&&this[0].parentNode){return this.domManip(arguments,false,function(bv){this.parentNode.insertBefore(bv,this.nextSibling)})}else{if(arguments.length){var e=this.pushStack(this,"after",arguments);e.push.apply(e,b.clean(arguments));return e}}},remove:function(e,bx){for(var bv=0,bw;(bw=this[bv])!=null;bv++){if(!e||b.filter(e,[bw]).length){if(!bx&&bw.nodeType===1){b.cleanData(bw.getElementsByTagName("*"));b.cleanData([bw])}if(bw.parentNode){bw.parentNode.removeChild(bw)}}}return this},empty:function(){for(var e=0,bv;(bv=this[e])!=null;e++){if(bv.nodeType===1){b.cleanData(bv.getElementsByTagName("*"))}while(bv.firstChild){bv.removeChild(bv.firstChild)}}return this},clone:function(bv,e){bv=bv==null?false:bv;e=e==null?bv:e;return this.map(function(){return b.clone(this,bv,e)})},html:function(e){return b.access(this,function(by){var bx=this[0]||{},bw=0,bv=this.length;if(by===L){return bx.nodeType===1?bx.innerHTML.replace(ah,""):null}if(typeof by==="string"&&!ae.test(by)&&(b.support.leadingWhitespace||!ar.test(by))&&!ax[(d.exec(by)||["",""])[1].toLowerCase()]){by=by.replace(R,"<$1>");try{for(;bw1&&bw0?this.clone(true):this).get();b(bC[bA])[bv](by);bz=bz.concat(by)}return this.pushStack(bz,e,bC.selector)}}});function bh(e){if(typeof e.getElementsByTagName!=="undefined"){return e.getElementsByTagName("*")}else{if(typeof e.querySelectorAll!=="undefined"){return e.querySelectorAll("*")}else{return[]}}}function az(e){if(e.type==="checkbox"||e.type==="radio"){e.defaultChecked=e.checked}}function D(e){var bv=(e.nodeName||"").toLowerCase();if(bv==="input"){az(e)}else{if(bv!=="script"&&typeof e.getElementsByTagName!=="undefined"){b.grep(e.getElementsByTagName("input"),az)}}}function am(e){var bv=av.createElement("div");ac.appendChild(bv);bv.innerHTML=e.outerHTML;return bv.firstChild}b.extend({clone:function(by,bA,bw){var e,bv,bx,bz=b.support.html5Clone||b.isXMLDoc(by)||!ai.test("<"+by.nodeName+">")?by.cloneNode(true):am(by);if((!b.support.noCloneEvent||!b.support.noCloneChecked)&&(by.nodeType===1||by.nodeType===11)&&!b.isXMLDoc(by)){aj(by,bz);e=bh(by);bv=bh(bz);for(bx=0;e[bx];++bx){if(bv[bx]){aj(e[bx],bv[bx])}}}if(bA){s(by,bz);if(bw){e=bh(by);bv=bh(bz);for(bx=0;e[bx];++bx){s(e[bx],bv[bx])}}}e=bv=null;return bz},clean:function(bI,bw,bv,bx){var bA,bH,bD,bJ=[];bw=bw||av;if(typeof bw.createElement==="undefined"){bw=bw.ownerDocument||bw[0]&&bw[0].ownerDocument||av}for(var bE=0,bG;(bG=bI[bE])!=null;bE++){if(typeof bG==="number"){bG+=""}if(!bG){continue}if(typeof bG==="string"){if(!W.test(bG)){bG=bw.createTextNode(bG)}else{bG=bG.replace(R,"<$1>");var bN=(d.exec(bG)||["",""])[1].toLowerCase(),bz=ax[bN]||ax._default,bK=bz[0],bB=bw.createElement("div"),bL=ac.childNodes,bM;if(bw===av){ac.appendChild(bB)}else{a(bw).appendChild(bB)}bB.innerHTML=bz[1]+bG+bz[2];while(bK--){bB=bB.lastChild}if(!b.support.tbody){var by=v.test(bG),e=bN==="table"&&!by?bB.firstChild&&bB.firstChild.childNodes:bz[1]===""&&!by?bB.childNodes:[];for(bD=e.length-1;bD>=0;--bD){if(b.nodeName(e[bD],"tbody")&&!e[bD].childNodes.length){e[bD].parentNode.removeChild(e[bD])}}}if(!b.support.leadingWhitespace&&ar.test(bG)){bB.insertBefore(bw.createTextNode(ar.exec(bG)[0]),bB.firstChild)}bG=bB.childNodes;if(bB){bB.parentNode.removeChild(bB);if(bL.length>0){bM=bL[bL.length-1];if(bM&&bM.parentNode){bM.parentNode.removeChild(bM)}}}}}var bF;if(!b.support.appendChecked){if(bG[0]&&typeof(bF=bG.length)==="number"){for(bD=0;bD1)};b.extend({cssHooks:{opacity:{get:function(bw,bv){if(bv){var e=Z(bw,"opacity");return e===""?"1":e}else{return bw.style.opacity}}}},cssNumber:{fillOpacity:true,fontWeight:true,lineHeight:true,opacity:true,orphans:true,widows:true,zIndex:true,zoom:true},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(bx,bw,bD,by){if(!bx||bx.nodeType===3||bx.nodeType===8||!bx.style){return}var bB,bC,bz=b.camelCase(bw),bv=bx.style,bE=b.cssHooks[bz];bw=b.cssProps[bz]||bz;if(bD!==L){bC=typeof bD;if(bC==="string"&&(bB=I.exec(bD))){bD=(+(bB[1]+1)*+bB[2])+parseFloat(b.css(bx,bw));bC="number"}if(bD==null||bC==="number"&&isNaN(bD)){return}if(bC==="number"&&!b.cssNumber[bz]){bD+="px"}if(!bE||!("set" in bE)||(bD=bE.set(bx,bD))!==L){try{bv[bw]=bD}catch(bA){}}}else{if(bE&&"get" in bE&&(bB=bE.get(bx,false,by))!==L){return bB}return bv[bw]}},css:function(by,bx,bv){var bw,e;bx=b.camelCase(bx);e=b.cssHooks[bx];bx=b.cssProps[bx]||bx;if(bx==="cssFloat"){bx="float"}if(e&&"get" in e&&(bw=e.get(by,true,bv))!==L){return bw}else{if(Z){return Z(by,bx)}}},swap:function(by,bx,bz){var e={},bw,bv;for(bv in bx){e[bv]=by.style[bv];by.style[bv]=bx[bv]}bw=bz.call(by);for(bv in bx){by.style[bv]=e[bv]}return bw}});b.curCSS=b.css;if(av.defaultView&&av.defaultView.getComputedStyle){aJ=function(bA,bw){var bv,bz,e,by,bx=bA.style;bw=bw.replace(y,"-$1").toLowerCase();if((bz=bA.ownerDocument.defaultView)&&(e=bz.getComputedStyle(bA,null))){bv=e.getPropertyValue(bw);if(bv===""&&!b.contains(bA.ownerDocument.documentElement,bA)){bv=b.style(bA,bw)}}if(!b.support.pixelMargin&&e&&aE.test(bw)&&a1.test(bv)){by=bx.width;bx.width=bv;bv=e.width;bx.width=by}return bv}}if(av.documentElement.currentStyle){aY=function(bz,bw){var bA,e,by,bv=bz.currentStyle&&bz.currentStyle[bw],bx=bz.style;if(bv==null&&bx&&(by=bx[bw])){bv=by}if(a1.test(bv)){bA=bx.left;e=bz.runtimeStyle&&bz.runtimeStyle.left;if(e){bz.runtimeStyle.left=bz.currentStyle.left}bx.left=bw==="fontSize"?"1em":bv;bv=bx.pixelLeft+"px";bx.left=bA;if(e){bz.runtimeStyle.left=e}}return bv===""?"auto":bv}}Z=aJ||aY;function af(by,bw,bv){var bz=bw==="width"?by.offsetWidth:by.offsetHeight,bx=bw==="width"?1:0,e=4;if(bz>0){if(bv!=="border"){for(;bx=1&&b.trim(bw.replace(al,""))===""){bx.removeAttribute("filter");if(bv&&!bv.filter){return}}bx.filter=al.test(bw)?bw.replace(al,e):bw+" "+e}}}b(function(){if(!b.support.reliableMarginRight){b.cssHooks.marginRight={get:function(bv,e){return b.swap(bv,{display:"inline-block"},function(){if(e){return Z(bv,"margin-right")}else{return bv.style.marginRight}})}}}});if(b.expr&&b.expr.filters){b.expr.filters.hidden=function(bw){var bv=bw.offsetWidth,e=bw.offsetHeight;return(bv===0&&e===0)||(!b.support.reliableHiddenOffsets&&((bw.style&&bw.style.display)||b.css(bw,"display"))==="none")};b.expr.filters.visible=function(e){return !b.expr.filters.hidden(e)}}b.each({margin:"",padding:"",border:"Width"},function(e,bv){b.cssHooks[e+bv]={expand:function(by){var bx,bz=typeof by==="string"?by.split(" "):[by],bw={};for(bx=0;bx<4;bx++){bw[e+G[bx]+bv]=bz[bx]||bz[bx-2]||bz[0]}return bw}}});var k=/%20/g,ap=/\[\]$/,bs=/\r?\n/g,bq=/#.*$/,aD=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,a0=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,aN=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,aR=/^(?:GET|HEAD)$/,c=/^\/\//,M=/\?/,a7=/)<[^<]*)*<\/script>/gi,p=/^(?:select|textarea)/i,h=/\s+/,br=/([?&])_=[^&]*/,K=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,z=b.fn.load,aa={},q={},aF,r,aW=["*/"]+["*"];try{aF=bm.href}catch(aw){aF=av.createElement("a");aF.href="";aF=aF.href}r=K.exec(aF.toLowerCase())||[];function f(e){return function(by,bA){if(typeof by!=="string"){bA=by;by="*"}if(b.isFunction(bA)){var bx=by.toLowerCase().split(h),bw=0,bz=bx.length,bv,bB,bC;for(;bw=0){var e=bw.slice(by,bw.length);bw=bw.slice(0,by)}var bx="GET";if(bz){if(b.isFunction(bz)){bA=bz;bz=L}else{if(typeof bz==="object"){bz=b.param(bz,b.ajaxSettings.traditional);bx="POST"}}}var bv=this;b.ajax({url:bw,type:bx,dataType:"html",data:bz,complete:function(bC,bB,bD){bD=bC.responseText;if(bC.isResolved()){bC.done(function(bE){bD=bE});bv.html(e?b("
").append(bD.replace(a7,"")).find(e):bD)}if(bA){bv.each(bA,[bD,bB,bC])}}});return this},serialize:function(){return b.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?b.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||p.test(this.nodeName)||a0.test(this.type))}).map(function(e,bv){var bw=b(this).val();return bw==null?null:b.isArray(bw)?b.map(bw,function(by,bx){return{name:bv.name,value:by.replace(bs,"\r\n")}}):{name:bv.name,value:bw.replace(bs,"\r\n")}}).get()}});b.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,bv){b.fn[bv]=function(bw){return this.on(bv,bw)}});b.each(["get","post"],function(e,bv){b[bv]=function(bw,by,bz,bx){if(b.isFunction(by)){bx=bx||bz;bz=by;by=L}return b.ajax({type:bv,url:bw,data:by,success:bz,dataType:bx})}});b.extend({getScript:function(e,bv){return b.get(e,L,bv,"script")},getJSON:function(e,bv,bw){return b.get(e,bv,bw,"json")},ajaxSetup:function(bv,e){if(e){an(bv,b.ajaxSettings)}else{e=bv;bv=b.ajaxSettings}an(bv,e);return bv},ajaxSettings:{url:aF,isLocal:aN.test(r[1]),global:true,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:true,async:true,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":aW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":bd.String,"text html":true,"text json":b.parseJSON,"text xml":b.parseXML},flatOptions:{context:true,url:true}},ajaxPrefilter:f(aa),ajaxTransport:f(q),ajax:function(bz,bx){if(typeof bz==="object"){bx=bz;bz=L}bx=bx||{};var bD=b.ajaxSetup({},bx),bS=bD.context||bD,bG=bS!==bD&&(bS.nodeType||bS instanceof b)?b(bS):b.event,bR=b.Deferred(),bN=b.Callbacks("once memory"),bB=bD.statusCode||{},bC,bH={},bO={},bQ,by,bL,bE,bI,bA=0,bw,bK,bJ={readyState:0,setRequestHeader:function(bT,bU){if(!bA){var e=bT.toLowerCase();bT=bO[e]=bO[e]||bT;bH[bT]=bU}return this},getAllResponseHeaders:function(){return bA===2?bQ:null},getResponseHeader:function(bT){var e;if(bA===2){if(!by){by={};while((e=aD.exec(bQ))){by[e[1].toLowerCase()]=e[2]}}e=by[bT.toLowerCase()]}return e===L?null:e},overrideMimeType:function(e){if(!bA){bD.mimeType=e}return this},abort:function(e){e=e||"abort";if(bL){bL.abort(e)}bF(0,e);return this}};function bF(bZ,bU,b0,bW){if(bA===2){return}bA=2;if(bE){clearTimeout(bE)}bL=L;bQ=bW||"";bJ.readyState=bZ>0?4:0;var bT,b4,b3,bX=bU,bY=b0?bk(bD,bJ,b0):L,bV,b2;if(bZ>=200&&bZ<300||bZ===304){if(bD.ifModified){if((bV=bJ.getResponseHeader("Last-Modified"))){b.lastModified[bC]=bV}if((b2=bJ.getResponseHeader("Etag"))){b.etag[bC]=b2}}if(bZ===304){bX="notmodified";bT=true}else{try{b4=F(bD,bY);bX="success";bT=true}catch(b1){bX="parsererror";b3=b1}}}else{b3=bX;if(!bX||bZ){bX="error";if(bZ<0){bZ=0}}}bJ.status=bZ;bJ.statusText=""+(bU||bX);if(bT){bR.resolveWith(bS,[b4,bX,bJ])}else{bR.rejectWith(bS,[bJ,bX,b3])}bJ.statusCode(bB);bB=L;if(bw){bG.trigger("ajax"+(bT?"Success":"Error"),[bJ,bD,bT?b4:b3])}bN.fireWith(bS,[bJ,bX]);if(bw){bG.trigger("ajaxComplete",[bJ,bD]);if(!(--b.active)){b.event.trigger("ajaxStop")}}}bR.promise(bJ);bJ.success=bJ.done;bJ.error=bJ.fail;bJ.complete=bN.add;bJ.statusCode=function(bT){if(bT){var e;if(bA<2){for(e in bT){bB[e]=[bB[e],bT[e]]}}else{e=bT[bJ.status];bJ.then(e,e)}}return this};bD.url=((bz||bD.url)+"").replace(bq,"").replace(c,r[1]+"//");bD.dataTypes=b.trim(bD.dataType||"*").toLowerCase().split(h);if(bD.crossDomain==null){bI=K.exec(bD.url.toLowerCase());bD.crossDomain=!!(bI&&(bI[1]!=r[1]||bI[2]!=r[2]||(bI[3]||(bI[1]==="http:"?80:443))!=(r[3]||(r[1]==="http:"?80:443))))}if(bD.data&&bD.processData&&typeof bD.data!=="string"){bD.data=b.param(bD.data,bD.traditional)}aX(aa,bD,bx,bJ);if(bA===2){return false}bw=bD.global;bD.type=bD.type.toUpperCase();bD.hasContent=!aR.test(bD.type);if(bw&&b.active++===0){b.event.trigger("ajaxStart")}if(!bD.hasContent){if(bD.data){bD.url+=(M.test(bD.url)?"&":"?")+bD.data;delete bD.data}bC=bD.url;if(bD.cache===false){var bv=b.now(),bP=bD.url.replace(br,"$1_="+bv);bD.url=bP+((bP===bD.url)?(M.test(bD.url)?"&":"?")+"_="+bv:"")}}if(bD.data&&bD.hasContent&&bD.contentType!==false||bx.contentType){bJ.setRequestHeader("Content-Type",bD.contentType)}if(bD.ifModified){bC=bC||bD.url;if(b.lastModified[bC]){bJ.setRequestHeader("If-Modified-Since",b.lastModified[bC])}if(b.etag[bC]){bJ.setRequestHeader("If-None-Match",b.etag[bC])}}bJ.setRequestHeader("Accept",bD.dataTypes[0]&&bD.accepts[bD.dataTypes[0]]?bD.accepts[bD.dataTypes[0]]+(bD.dataTypes[0]!=="*"?", "+aW+"; q=0.01":""):bD.accepts["*"]);for(bK in bD.headers){bJ.setRequestHeader(bK,bD.headers[bK])}if(bD.beforeSend&&(bD.beforeSend.call(bS,bJ,bD)===false||bA===2)){bJ.abort();return false}for(bK in {success:1,error:1,complete:1}){bJ[bK](bD[bK])}bL=aX(q,bD,bx,bJ);if(!bL){bF(-1,"No Transport")}else{bJ.readyState=1;if(bw){bG.trigger("ajaxSend",[bJ,bD])}if(bD.async&&bD.timeout>0){bE=setTimeout(function(){bJ.abort("timeout")},bD.timeout)}try{bA=1;bL.send(bH,bF)}catch(bM){if(bA<2){bF(-1,bM)}else{throw bM}}}return bJ},param:function(e,bw){var bv=[],by=function(bz,bA){bA=b.isFunction(bA)?bA():bA;bv[bv.length]=encodeURIComponent(bz)+"="+encodeURIComponent(bA)};if(bw===L){bw=b.ajaxSettings.traditional}if(b.isArray(e)||(e.jquery&&!b.isPlainObject(e))){b.each(e,function(){by(this.name,this.value)})}else{for(var bx in e){u(bx,e[bx],bw,by)}}return bv.join("&").replace(k,"+")}});function u(bw,by,bv,bx){if(b.isArray(by)){b.each(by,function(bA,bz){if(bv||ap.test(bw)){bx(bw,bz)}else{u(bw+"["+(typeof bz==="object"?bA:"")+"]",bz,bv,bx)}})}else{if(!bv&&b.type(by)==="object"){for(var e in by){u(bw+"["+e+"]",by[e],bv,bx)}}else{bx(bw,by)}}}b.extend({active:0,lastModified:{},etag:{}});function bk(bD,bC,bz){var bv=bD.contents,bB=bD.dataTypes,bw=bD.responseFields,by,bA,bx,e;for(bA in bw){if(bA in bz){bC[bw[bA]]=bz[bA]}}while(bB[0]==="*"){bB.shift();if(by===L){by=bD.mimeType||bC.getResponseHeader("content-type")}}if(by){for(bA in bv){if(bv[bA]&&bv[bA].test(by)){bB.unshift(bA);break}}}if(bB[0] in bz){bx=bB[0]}else{for(bA in bz){if(!bB[0]||bD.converters[bA+" "+bB[0]]){bx=bA;break}if(!e){e=bA}}bx=bx||e}if(bx){if(bx!==bB[0]){bB.unshift(bx)}return bz[bx]}}function F(bH,bz){if(bH.dataFilter){bz=bH.dataFilter(bz,bH.dataType)}var bD=bH.dataTypes,bG={},bA,bE,bw=bD.length,bB,bC=bD[0],bx,by,bF,bv,e;for(bA=1;bA=bw.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();bw.animatedProperties[this.prop]=true;for(bA in bw.animatedProperties){if(bw.animatedProperties[bA]!==true){e=false}}if(e){if(bw.overflow!=null&&!b.support.shrinkWrapBlocks){b.each(["","X","Y"],function(bC,bD){bz.style["overflow"+bD]=bw.overflow[bC]})}if(bw.hide){b(bz).hide()}if(bw.hide||bw.show){for(bA in bw.animatedProperties){b.style(bz,bA,bw.orig[bA]);b.removeData(bz,"fxshow"+bA,true);b.removeData(bz,"toggle"+bA,true)}}bv=bw.complete;if(bv){bw.complete=false;bv.call(bz)}}return false}else{if(bw.duration==Infinity){this.now=bx}else{bB=bx-this.startTime;this.state=bB/bw.duration;this.pos=b.easing[bw.animatedProperties[this.prop]](this.state,bB,0,1,bw.duration);this.now=this.start+((this.end-this.start)*this.pos)}this.update()}return true}};b.extend(b.fx,{tick:function(){var bw,bv=b.timers,e=0;for(;e").appendTo(e),bw=bv.css("display");bv.remove();if(bw==="none"||bw===""){if(!ba){ba=av.createElement("iframe");ba.frameBorder=ba.width=ba.height=0}e.appendChild(ba);if(!m||!ba.createElement){m=(ba.contentWindow||ba.contentDocument).document;m.write((b.support.boxModel?"":"")+"");m.close()}bv=m.createElement(bx);m.body.appendChild(bv);bw=b.css(bv,"display");e.removeChild(ba)}Q[bx]=bw}return Q[bx]}var a8,V=/^t(?:able|d|h)$/i,ad=/^(?:body|html)$/i;if("getBoundingClientRect" in av.documentElement){a8=function(by,bH,bw,bB){try{bB=by.getBoundingClientRect()}catch(bF){}if(!bB||!b.contains(bw,by)){return bB?{top:bB.top,left:bB.left}:{top:0,left:0}}var bC=bH.body,bD=aL(bH),bA=bw.clientTop||bC.clientTop||0,bE=bw.clientLeft||bC.clientLeft||0,bv=bD.pageYOffset||b.support.boxModel&&bw.scrollTop||bC.scrollTop,bz=bD.pageXOffset||b.support.boxModel&&bw.scrollLeft||bC.scrollLeft,bG=bB.top+bv-bA,bx=bB.left+bz-bE;return{top:bG,left:bx}}}else{a8=function(bz,bE,bx){var bC,bw=bz.offsetParent,bv=bz,bA=bE.body,bB=bE.defaultView,e=bB?bB.getComputedStyle(bz,null):bz.currentStyle,bD=bz.offsetTop,by=bz.offsetLeft;while((bz=bz.parentNode)&&bz!==bA&&bz!==bx){if(b.support.fixedPosition&&e.position==="fixed"){break}bC=bB?bB.getComputedStyle(bz,null):bz.currentStyle;bD-=bz.scrollTop;by-=bz.scrollLeft;if(bz===bw){bD+=bz.offsetTop;by+=bz.offsetLeft;if(b.support.doesNotAddBorder&&!(b.support.doesAddBorderForTableAndCells&&V.test(bz.nodeName))){bD+=parseFloat(bC.borderTopWidth)||0;by+=parseFloat(bC.borderLeftWidth)||0}bv=bw;bw=bz.offsetParent}if(b.support.subtractsBorderForOverflowNotVisible&&bC.overflow!=="visible"){bD+=parseFloat(bC.borderTopWidth)||0;by+=parseFloat(bC.borderLeftWidth)||0}e=bC}if(e.position==="relative"||e.position==="static"){bD+=bA.offsetTop;by+=bA.offsetLeft}if(b.support.fixedPosition&&e.position==="fixed"){bD+=Math.max(bx.scrollTop,bA.scrollTop);by+=Math.max(bx.scrollLeft,bA.scrollLeft)}return{top:bD,left:by}}}b.fn.offset=function(e){if(arguments.length){return e===L?this:this.each(function(bx){b.offset.setOffset(this,e,bx)})}var bv=this[0],bw=bv&&bv.ownerDocument;if(!bw){return null}if(bv===bw.body){return b.offset.bodyOffset(bv)}return a8(bv,bw,bw.documentElement)};b.offset={bodyOffset:function(e){var bw=e.offsetTop,bv=e.offsetLeft;if(b.support.doesNotIncludeMarginInBodyOffset){bw+=parseFloat(b.css(e,"marginTop"))||0;bv+=parseFloat(b.css(e,"marginLeft"))||0}return{top:bw,left:bv}},setOffset:function(bx,bG,bA){var bB=b.css(bx,"position");if(bB==="static"){bx.style.position="relative"}var bz=b(bx),bv=bz.offset(),e=b.css(bx,"top"),bE=b.css(bx,"left"),bF=(bB==="absolute"||bB==="fixed")&&b.inArray("auto",[e,bE])>-1,bD={},bC={},bw,by;if(bF){bC=bz.position();bw=bC.top;by=bC.left}else{bw=parseFloat(e)||0;by=parseFloat(bE)||0}if(b.isFunction(bG)){bG=bG.call(bx,bA,bv)}if(bG.top!=null){bD.top=(bG.top-bv.top)+bw}if(bG.left!=null){bD.left=(bG.left-bv.left)+by}if("using" in bG){bG.using.call(bx,bD)}else{bz.css(bD)}}};b.fn.extend({position:function(){if(!this[0]){return null}var bw=this[0],bv=this.offsetParent(),bx=this.offset(),e=ad.test(bv[0].nodeName)?{top:0,left:0}:bv.offset();bx.top-=parseFloat(b.css(bw,"marginTop"))||0;bx.left-=parseFloat(b.css(bw,"marginLeft"))||0;e.top+=parseFloat(b.css(bv[0],"borderTopWidth"))||0;e.left+=parseFloat(b.css(bv[0],"borderLeftWidth"))||0;return{top:bx.top-e.top,left:bx.left-e.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||av.body;while(e&&(!ad.test(e.nodeName)&&b.css(e,"position")==="static")){e=e.offsetParent}return e})}});b.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(bw,bv){var e=/Y/.test(bv);b.fn[bw]=function(bx){return b.access(this,function(by,bB,bA){var bz=aL(by);if(bA===L){return bz?(bv in bz)?bz[bv]:b.support.boxModel&&bz.document.documentElement[bB]||bz.document.body[bB]:by[bB]}if(bz){bz.scrollTo(!e?bA:b(bz).scrollLeft(),e?bA:b(bz).scrollTop())}else{by[bB]=bA}},bw,bx,arguments.length,null)}});function aL(e){return b.isWindow(e)?e:e.nodeType===9?e.defaultView||e.parentWindow:false}b.each({Height:"height",Width:"width"},function(bw,bx){var bv="client"+bw,e="scroll"+bw,by="offset"+bw;b.fn["inner"+bw]=function(){var bz=this[0];return bz?bz.style?parseFloat(b.css(bz,bx,"padding")):this[bx]():null};b.fn["outer"+bw]=function(bA){var bz=this[0];return bz?bz.style?parseFloat(b.css(bz,bx,bA?"margin":"border")):this[bx]():null};b.fn[bx]=function(bz){return b.access(this,function(bC,bB,bD){var bF,bE,bG,bA;if(b.isWindow(bC)){bF=bC.document;bE=bF.documentElement[bv];return b.support.boxModel&&bE||bF.body&&bF.body[bv]||bE}if(bC.nodeType===9){bF=bC.documentElement;if(bF[bv]>=bF[e]){return bF[bv]}return Math.max(bC.body[e],bF[e],bC.body[by],bF[by])}if(bD===L){bG=b.css(bC,bB);bA=parseFloat(bG);return b.isNumeric(bA)?bA:bG}b(bC).css(bB,bD)},bx,bz,arguments.length,null)}});bd.jQuery=bd.$=b;if(typeof define==="function"&&define.amd&&define.amd.jQuery){define("jquery",[],function(){return b})}})(window);/*! + * jQuery UI 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI + */ +(function(a,d){a.ui=a.ui||{};if(a.ui.version){return}a.extend(a.ui,{version:"1.8.18",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(e,f){return typeof e==="number"?this.each(function(){var g=this;setTimeout(function(){a(g).focus();if(f){f.call(g)}},e)}):this._focus.apply(this,arguments)},scrollParent:function(){var e;if((a.browser.msie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){e=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(a.curCSS(this,"position",1))&&(/(auto|scroll)/).test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0)}else{e=this.parents().filter(function(){return(/(auto|scroll)/).test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0)}return(/fixed/).test(this.css("position"))||!e.length?a(document):e},zIndex:function(h){if(h!==d){return this.css("zIndex",h)}if(this.length){var f=a(this[0]),e,g;while(f.length&&f[0]!==document){e=f.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){g=parseInt(f.css("zIndex"),10);if(!isNaN(g)&&g!==0){return g}}f=f.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});a.each(["Width","Height"],function(g,e){var f=e==="Width"?["Left","Right"]:["Top","Bottom"],h=e.toLowerCase(),k={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};function j(m,l,i,n){a.each(f,function(){l-=parseFloat(a.curCSS(m,"padding"+this,true))||0;if(i){l-=parseFloat(a.curCSS(m,"border"+this+"Width",true))||0}if(n){l-=parseFloat(a.curCSS(m,"margin"+this,true))||0}});return l}a.fn["inner"+e]=function(i){if(i===d){return k["inner"+e].call(this)}return this.each(function(){a(this).css(h,j(this,i)+"px")})};a.fn["outer"+e]=function(i,l){if(typeof i!=="number"){return k["outer"+e].call(this,i)}return this.each(function(){a(this).css(h,j(this,i,true,l)+"px")})}});function c(g,e){var j=g.nodeName.toLowerCase();if("area"===j){var i=g.parentNode,h=i.name,f;if(!g.href||!h||i.nodeName.toLowerCase()!=="map"){return false}f=a("img[usemap=#"+h+"]")[0];return !!f&&b(f)}return(/input|select|textarea|button|object/.test(j)?!g.disabled:"a"==j?g.href||e:e)&&b(g)}function b(e){return !a(e).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.extend(a.expr[":"],{data:function(g,f,e){return !!a.data(g,e[3])},focusable:function(e){return c(e,!isNaN(a.attr(e,"tabindex")))},tabbable:function(g){var e=a.attr(g,"tabindex"),f=isNaN(e);return(f||e>=0)&&c(g,!f)}});a(function(){var e=document.body,f=e.appendChild(f=document.createElement("div"));f.offsetHeight;a.extend(f.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});a.support.minHeight=f.offsetHeight===100;a.support.selectstart="onselectstart" in f;e.removeChild(f).style.display="none"});a.extend(a.ui,{plugin:{add:function(f,g,j){var h=a.ui[f].prototype;for(var e in j){h.plugins[e]=h.plugins[e]||[];h.plugins[e].push([g,j[e]])}},call:function(e,g,f){var j=e.plugins[g];if(!j||!e.element[0].parentNode){return}for(var h=0;h0){return true}h[e]=1;g=(h[e]>0);h[e]=0;return g},isOverAxis:function(f,e,g){return(f>e)&&(f<(e+g))},isOver:function(j,f,i,h,e,g){return a.ui.isOverAxis(j,i,e)&&a.ui.isOverAxis(f,h,g)}})})(jQuery);/*! + * jQuery UI Widget 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Widget + */ +(function(b,d){if(b.cleanData){var c=b.cleanData;b.cleanData=function(f){for(var g=0,h;(h=f[g])!=null;g++){try{b(h).triggerHandler("remove")}catch(j){}}c(f)}}else{var a=b.fn.remove;b.fn.remove=function(e,f){return this.each(function(){if(!f){if(!e||b.filter(e,[this]).length){b("*",this).add([this]).each(function(){try{b(this).triggerHandler("remove")}catch(g){}})}}return a.call(b(this),e,f)})}}b.widget=function(f,h,e){var g=f.split(".")[0],j;f=f.split(".")[1];j=g+"-"+f;if(!e){e=h;h=b.Widget}b.expr[":"][j]=function(k){return !!b.data(k,f)};b[g]=b[g]||{};b[g][f]=function(k,l){if(arguments.length){this._createWidget(k,l)}};var i=new h();i.options=b.extend(true,{},i.options);b[g][f].prototype=b.extend(true,i,{namespace:g,widgetName:f,widgetEventPrefix:b[g][f].prototype.widgetEventPrefix||f,widgetBaseClass:j},e);b.widget.bridge(f,b[g][f])};b.widget.bridge=function(f,e){b.fn[f]=function(i){var g=typeof i==="string",h=Array.prototype.slice.call(arguments,1),j=this;i=!g&&h.length?b.extend.apply(null,[true,i].concat(h)):i;if(g&&i.charAt(0)==="_"){return j}if(g){this.each(function(){var k=b.data(this,f),l=k&&b.isFunction(k[i])?k[i].apply(k,h):k;if(l!==k&&l!==d){j=l;return false}})}else{this.each(function(){var k=b.data(this,f);if(k){k.option(i||{})._init()}else{b.data(this,f,new e(i,this))}})}return j}};b.Widget=function(e,f){if(arguments.length){this._createWidget(e,f)}};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(f,g){b.data(g,this.widgetName,this);this.element=b(g);this.options=b.extend(true,{},this.options,this._getCreateOptions(),f);var e=this;this.element.bind("remove."+this.widgetName,function(){e.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(f,g){var e=f;if(arguments.length===0){return b.extend({},this.options)}if(typeof f==="string"){if(g===d){return this.options[f]}e={};e[f]=g}this._setOptions(e);return this},_setOptions:function(f){var e=this;b.each(f,function(g,h){e._setOption(g,h)});return this},_setOption:function(e,f){this.options[e]=f;if(e==="disabled"){this.widget()[f?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",f)}return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(e,f,g){var j,i,h=this.options[e];g=g||{};f=b.Event(f);f.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase();f.target=this.element[0];i=f.originalEvent;if(i){for(j in i){if(!(j in f)){f[j]=i[j]}}}this.element.trigger(f,g);return !(b.isFunction(h)&&h.call(this.element[0],f,g)===false||f.isDefaultPrevented())}}})(jQuery);/*! + * jQuery UI Mouse 1.8.18 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Mouse + * + * Depends: + * jquery.ui.widget.js + */ +(function(b,c){var a=false;b(document).mouseup(function(d){a=false});b.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var d=this;this.element.bind("mousedown."+this.widgetName,function(e){return d._mouseDown(e)}).bind("click."+this.widgetName,function(e){if(true===b.data(e.target,d.widgetName+".preventClickEvent")){b.removeData(e.target,d.widgetName+".preventClickEvent");e.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(f){if(a){return}(this._mouseStarted&&this._mouseUp(f));this._mouseDownEvent=f;var e=this,g=(f.which==1),d=(typeof this.options.cancel=="string"&&f.target.nodeName?b(f.target).closest(this.options.cancel).length:false);if(!g||d||!this._mouseCapture(f)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){e.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(f)&&this._mouseDelayMet(f)){this._mouseStarted=(this._mouseStart(f)!==false);if(!this._mouseStarted){f.preventDefault();return true}}if(true===b.data(f.target,this.widgetName+".preventClickEvent")){b.removeData(f.target,this.widgetName+".preventClickEvent")}this._mouseMoveDelegate=function(h){return e._mouseMove(h)};this._mouseUpDelegate=function(h){return e._mouseUp(h)};b(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);f.preventDefault();a=true;return true},_mouseMove:function(d){if(b.browser.msie&&!(document.documentMode>=9)&&!d.button){return this._mouseUp(d)}if(this._mouseStarted){this._mouseDrag(d);return d.preventDefault()}if(this._mouseDistanceMet(d)&&this._mouseDelayMet(d)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,d)!==false);(this._mouseStarted?this._mouseDrag(d):this._mouseUp(d))}return !this._mouseStarted},_mouseUp:function(d){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;if(d.target==this._mouseDownEvent.target){b.data(d.target,this.widgetName+".preventClickEvent",true)}this._mouseStop(d)}return false},_mouseDistanceMet:function(d){return(Math.max(Math.abs(this._mouseDownEvent.pageX-d.pageX),Math.abs(this._mouseDownEvent.pageY-d.pageY))>=this.options.distance)},_mouseDelayMet:function(d){return this.mouseDelayMet},_mouseStart:function(d){},_mouseDrag:function(d){},_mouseStop:function(d){},_mouseCapture:function(d){return true}})})(jQuery);(function(c,d){c.widget("ui.resizable",c.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,containment:false,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1000},_create:function(){var f=this,k=this.options;this.element.addClass("ui-resizable");c.extend(this,{_aspectRatio:!!(k.aspectRatio),aspectRatio:k.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:k.helper||k.ghost||k.animate?k.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){this.element.wrap(c('
').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=k.handles||(!c(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all"){this.handles="n,e,s,w,se,sw,ne,nw"}var l=this.handles.split(",");this.handles={};for(var g=0;g
');if(/sw|se|ne|nw/.test(j)){h.css({zIndex:++k.zIndex})}if("se"==j){h.addClass("ui-icon ui-icon-gripsmall-diagonal-se")}this.handles[j]=".ui-resizable-"+j;this.element.append(h)}}this._renderAxis=function(q){q=q||this.element;for(var n in this.handles){if(this.handles[n].constructor==String){this.handles[n]=c(this.handles[n],this.element).show()}if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var o=c(this.handles[n],this.element),p=0;p=/sw|ne|nw|se|n|s/.test(n)?o.outerHeight():o.outerWidth();var m=["padding",/ne|nw|n/.test(n)?"Top":/se|sw|s/.test(n)?"Bottom":/^e$/.test(n)?"Right":"Left"].join("");q.css(m,p);this._proportionallyResize()}if(!c(this.handles[n]).length){continue}}};this._renderAxis(this.element);this._handles=c(".ui-resizable-handle",this.element).disableSelection();this._handles.mouseover(function(){if(!f.resizing){if(this.className){var i=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)}f.axis=i&&i[1]?i[1]:"se"}});if(k.autoHide){this._handles.hide();c(this.element).addClass("ui-resizable-autohide").hover(function(){if(k.disabled){return}c(this).removeClass("ui-resizable-autohide");f._handles.show()},function(){if(k.disabled){return}if(!f.resizing){c(this).addClass("ui-resizable-autohide");f._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();var e=function(g){c(g).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){e(this.element);var f=this.element;f.after(this.originalElement.css({position:f.css("position"),width:f.outerWidth(),height:f.outerHeight(),top:f.css("top"),left:f.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);e(this.originalElement);return this},_mouseCapture:function(f){var g=false;for(var e in this.handles){if(c(this.handles[e])[0]==f.target){g=true}}return !this.options.disabled&&g},_mouseStart:function(g){var j=this.options,f=this.element.position(),e=this.element;this.resizing=true;this.documentScroll={top:c(document).scrollTop(),left:c(document).scrollLeft()};if(e.is(".ui-draggable")||(/absolute/).test(e.css("position"))){e.css({position:"absolute",top:f.top,left:f.left})}this._renderProxy();var k=b(this.helper.css("left")),h=b(this.helper.css("top"));if(j.containment){k+=c(j.containment).scrollLeft()||0;h+=c(j.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:k,top:h};this.size=this._helper?{width:e.outerWidth(),height:e.outerHeight()}:{width:e.width(),height:e.height()};this.originalSize=this._helper?{width:e.outerWidth(),height:e.outerHeight()}:{width:e.width(),height:e.height()};this.originalPosition={left:k,top:h};this.sizeDiff={width:e.outerWidth()-e.width(),height:e.outerHeight()-e.height()};this.originalMousePosition={left:g.pageX,top:g.pageY};this.aspectRatio=(typeof j.aspectRatio=="number")?j.aspectRatio:((this.originalSize.width/this.originalSize.height)||1);var i=c(".ui-resizable-"+this.axis).css("cursor");c("body").css("cursor",i=="auto"?this.axis+"-resize":i);e.addClass("ui-resizable-resizing");this._propagate("start",g);return true},_mouseDrag:function(e){var h=this.helper,g=this.options,m={},q=this,j=this.originalMousePosition,n=this.axis;var r=(e.pageX-j.left)||0,p=(e.pageY-j.top)||0;var i=this._change[n];if(!i){return false}var l=i.apply(this,[e,r,p]),k=c.browser.msie&&c.browser.version<7,f=this.sizeDiff;this._updateVirtualBoundaries(e.shiftKey);if(this._aspectRatio||e.shiftKey){l=this._updateRatio(l,e)}l=this._respectSize(l,e);this._propagate("resize",e);h.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});if(!this._helper&&this._proportionallyResizeElements.length){this._proportionallyResize()}this._updateCache(l);this._trigger("resize",e,this.ui());return false},_mouseStop:function(h){this.resizing=false;var i=this.options,m=this;if(this._helper){var g=this._proportionallyResizeElements,e=g.length&&(/textarea/i).test(g[0].nodeName),f=e&&c.ui.hasScroll(g[0],"left")?0:m.sizeDiff.height,k=e?0:m.sizeDiff.width;var n={width:(m.helper.width()-k),height:(m.helper.height()-f)},j=(parseInt(m.element.css("left"),10)+(m.position.left-m.originalPosition.left))||null,l=(parseInt(m.element.css("top"),10)+(m.position.top-m.originalPosition.top))||null;if(!i.animate){this.element.css(c.extend(n,{top:l,left:j}))}m.helper.height(m.size.height);m.helper.width(m.size.width);if(this._helper&&!i.animate){this._proportionallyResize()}}c("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",h);if(this._helper){this.helper.remove()}return false},_updateVirtualBoundaries:function(g){var j=this.options,i,h,f,k,e;e={minWidth:a(j.minWidth)?j.minWidth:0,maxWidth:a(j.maxWidth)?j.maxWidth:Infinity,minHeight:a(j.minHeight)?j.minHeight:0,maxHeight:a(j.maxHeight)?j.maxHeight:Infinity};if(this._aspectRatio||g){i=e.minHeight*this.aspectRatio;f=e.minWidth/this.aspectRatio;h=e.maxHeight*this.aspectRatio;k=e.maxWidth/this.aspectRatio;if(i>e.minWidth){e.minWidth=i}if(f>e.minHeight){e.minHeight=f}if(hl.width),s=a(l.height)&&i.minHeight&&(i.minHeight>l.height);if(h){l.width=i.minWidth}if(s){l.height=i.minHeight}if(t){l.width=i.maxWidth}if(m){l.height=i.maxHeight}var f=this.originalPosition.left+this.originalSize.width,p=this.position.top+this.size.height;var k=/sw|nw|w/.test(q),e=/nw|ne|n/.test(q);if(h&&k){l.left=f-i.minWidth}if(t&&k){l.left=f-i.maxWidth}if(s&&e){l.top=p-i.minHeight}if(m&&e){l.top=p-i.maxHeight}var n=!l.width&&!l.height;if(n&&!l.left&&l.top){l.top=null}else{if(n&&!l.top&&l.left){l.left=null}}return l},_proportionallyResize:function(){var k=this.options;if(!this._proportionallyResizeElements.length){return}var g=this.helper||this.element;for(var f=0;f');var e=c.browser.msie&&c.browser.version<7,g=(e?1:0),h=(e?2:-1);this.helper.addClass(this._helper).css({width:this.element.outerWidth()+h,height:this.element.outerHeight()+h,position:"absolute",left:this.elementOffset.left-g+"px",top:this.elementOffset.top-g+"px",zIndex:++i.zIndex});this.helper.appendTo("body").disableSelection()}else{this.helper=this.element}},_change:{e:function(g,f,e){return{width:this.originalSize.width+f}},w:function(h,f,e){var j=this.options,g=this.originalSize,i=this.originalPosition;return{left:i.left+f,width:g.width-f}},n:function(h,f,e){var j=this.options,g=this.originalSize,i=this.originalPosition;return{top:i.top+e,height:g.height-e}},s:function(g,f,e){return{height:this.originalSize.height+e}},se:function(g,f,e){return c.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[g,f,e]))},sw:function(g,f,e){return c.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[g,f,e]))},ne:function(g,f,e){return c.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[g,f,e]))},nw:function(g,f,e){return c.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[g,f,e]))}},_propagate:function(f,e){c.ui.plugin.call(this,f,[e,this.ui()]);(f!="resize"&&this._trigger(f,e,this.ui()))},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});c.extend(c.ui.resizable,{version:"1.8.18"});c.ui.plugin.add("resizable","alsoResize",{start:function(f,g){var e=c(this).data("resizable"),i=e.options;var h=function(j){c(j).each(function(){var k=c(this);k.data("resizable-alsoresize",{width:parseInt(k.width(),10),height:parseInt(k.height(),10),left:parseInt(k.css("left"),10),top:parseInt(k.css("top"),10)})})};if(typeof(i.alsoResize)=="object"&&!i.alsoResize.parentNode){if(i.alsoResize.length){i.alsoResize=i.alsoResize[0];h(i.alsoResize)}else{c.each(i.alsoResize,function(j){h(j)})}}else{h(i.alsoResize)}},resize:function(g,i){var f=c(this).data("resizable"),j=f.options,h=f.originalSize,l=f.originalPosition;var k={height:(f.size.height-h.height)||0,width:(f.size.width-h.width)||0,top:(f.position.top-l.top)||0,left:(f.position.left-l.left)||0},e=function(m,n){c(m).each(function(){var q=c(this),r=c(this).data("resizable-alsoresize"),p={},o=n&&n.length?n:q.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];c.each(o,function(s,u){var t=(r[u]||0)+(k[u]||0);if(t&&t>=0){p[u]=t||null}});q.css(p)})};if(typeof(j.alsoResize)=="object"&&!j.alsoResize.nodeType){c.each(j.alsoResize,function(m,n){e(m,n)})}else{e(j.alsoResize)}},stop:function(e,f){c(this).removeData("resizable-alsoresize")}});c.ui.plugin.add("resizable","animate",{stop:function(i,n){var p=c(this).data("resizable"),j=p.options;var h=p._proportionallyResizeElements,e=h.length&&(/textarea/i).test(h[0].nodeName),f=e&&c.ui.hasScroll(h[0],"left")?0:p.sizeDiff.height,l=e?0:p.sizeDiff.width;var g={width:(p.size.width-l),height:(p.size.height-f)},k=(parseInt(p.element.css("left"),10)+(p.position.left-p.originalPosition.left))||null,m=(parseInt(p.element.css("top"),10)+(p.position.top-p.originalPosition.top))||null;p.element.animate(c.extend(g,m&&k?{top:m,left:k}:{}),{duration:j.animateDuration,easing:j.animateEasing,step:function(){var o={width:parseInt(p.element.css("width"),10),height:parseInt(p.element.css("height"),10),top:parseInt(p.element.css("top"),10),left:parseInt(p.element.css("left"),10)};if(h&&h.length){c(h[0]).css({width:o.width,height:o.height})}p._updateCache(o);p._propagate("resize",i)}})}});c.ui.plugin.add("resizable","containment",{start:function(f,r){var t=c(this).data("resizable"),j=t.options,l=t.element;var g=j.containment,k=(g instanceof c)?g.get(0):(/parent/.test(g))?l.parent().get(0):g;if(!k){return}t.containerElement=c(k);if(/document/.test(g)||g==document){t.containerOffset={left:0,top:0};t.containerPosition={left:0,top:0};t.parentData={element:c(document),left:0,top:0,width:c(document).width(),height:c(document).height()||document.body.parentNode.scrollHeight}}else{var n=c(k),i=[];c(["Top","Right","Left","Bottom"]).each(function(p,o){i[p]=b(n.css("padding"+o))});t.containerOffset=n.offset();t.containerPosition=n.position();t.containerSize={height:(n.innerHeight()-i[3]),width:(n.innerWidth()-i[1])};var q=t.containerOffset,e=t.containerSize.height,m=t.containerSize.width,h=(c.ui.hasScroll(k,"left")?k.scrollWidth:m),s=(c.ui.hasScroll(k)?k.scrollHeight:e);t.parentData={element:k,left:q.left,top:q.top,width:h,height:s}}},resize:function(g,q){var t=c(this).data("resizable"),i=t.options,f=t.containerSize,p=t.containerOffset,m=t.size,n=t.position,r=t._aspectRatio||g.shiftKey,e={top:0,left:0},h=t.containerElement;if(h[0]!=document&&(/static/).test(h.css("position"))){e=p}if(n.left<(t._helper?p.left:0)){t.size.width=t.size.width+(t._helper?(t.position.left-p.left):(t.position.left-e.left));if(r){t.size.height=t.size.width/i.aspectRatio}t.position.left=i.helper?p.left:0}if(n.top<(t._helper?p.top:0)){t.size.height=t.size.height+(t._helper?(t.position.top-p.top):t.position.top);if(r){t.size.width=t.size.height*i.aspectRatio}t.position.top=t._helper?p.top:0}t.offset.left=t.parentData.left+t.position.left;t.offset.top=t.parentData.top+t.position.top;var l=Math.abs((t._helper?t.offset.left-e.left:(t.offset.left-e.left))+t.sizeDiff.width),s=Math.abs((t._helper?t.offset.top-e.top:(t.offset.top-p.top))+t.sizeDiff.height);var k=t.containerElement.get(0)==t.element.parent().get(0),j=/relative|absolute/.test(t.containerElement.css("position"));if(k&&j){l-=t.parentData.left}if(l+t.size.width>=t.parentData.width){t.size.width=t.parentData.width-l;if(r){t.size.height=t.size.width/t.aspectRatio}}if(s+t.size.height>=t.parentData.height){t.size.height=t.parentData.height-s;if(r){t.size.width=t.size.height*t.aspectRatio}}},stop:function(f,n){var q=c(this).data("resizable"),g=q.options,l=q.position,m=q.containerOffset,e=q.containerPosition,i=q.containerElement;var j=c(q.helper),r=j.offset(),p=j.outerWidth()-q.sizeDiff.width,k=j.outerHeight()-q.sizeDiff.height;if(q._helper&&!g.animate&&(/relative/).test(i.css("position"))){c(this).css({left:r.left-e.left-m.left,width:p,height:k})}if(q._helper&&!g.animate&&(/static/).test(i.css("position"))){c(this).css({left:r.left-e.left-m.left,width:p,height:k})}}});c.ui.plugin.add("resizable","ghost",{start:function(g,h){var e=c(this).data("resizable"),i=e.options,f=e.size;e.ghost=e.originalElement.clone();e.ghost.css({opacity:0.25,display:"block",position:"relative",height:f.height,width:f.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof i.ghost=="string"?i.ghost:"");e.ghost.appendTo(e.helper)},resize:function(f,g){var e=c(this).data("resizable"),h=e.options;if(e.ghost){e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})}},stop:function(f,g){var e=c(this).data("resizable"),h=e.options;if(e.ghost&&e.helper){e.helper.get(0).removeChild(e.ghost.get(0))}}});c.ui.plugin.add("resizable","grid",{resize:function(e,m){var p=c(this).data("resizable"),h=p.options,k=p.size,i=p.originalSize,j=p.originalPosition,n=p.axis,l=h._aspectRatio||e.shiftKey;h.grid=typeof h.grid=="number"?[h.grid,h.grid]:h.grid;var g=Math.round((k.width-i.width)/(h.grid[0]||1))*(h.grid[0]||1),f=Math.round((k.height-i.height)/(h.grid[1]||1))*(h.grid[1]||1);if(/^(se|s|e)$/.test(n)){p.size.width=i.width+g;p.size.height=i.height+f}else{if(/^(ne)$/.test(n)){p.size.width=i.width+g;p.size.height=i.height+f;p.position.top=j.top-f}else{if(/^(sw)$/.test(n)){p.size.width=i.width+g;p.size.height=i.height+f;p.position.left=j.left-g}else{p.size.width=i.width+g;p.size.height=i.height+f;p.position.top=j.top-f;p.position.left=j.left-g}}}}});var b=function(e){return parseInt(e,10)||0};var a=function(e){return !isNaN(parseInt(e,10))}})(jQuery);/*! + * jQuery hashchange event - v1.3 - 7/21/2010 + * http://benalman.com/projects/jquery-hashchange-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function($,e,b){var c="hashchange",h=document,f,g=$.event.special,i=h.documentMode,d="on"+c in e&&(i===b||i>7);function a(j){j=j||location.href;return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")}$.fn[c]=function(j){return j?this.bind(c,j):this.trigger(c)};$.fn[c].delay=50;g[c]=$.extend(g[c],{setup:function(){if(d){return false}$(f.start)},teardown:function(){if(d){return false}$(f.stop)}});f=(function(){var j={},p,m=a(),k=function(q){return q},l=k,o=k;j.start=function(){p||n()};j.stop=function(){p&&clearTimeout(p);p=b};function n(){var r=a(),q=o(m);if(r!==m){l(m=r,q);$(e).trigger(c)}else{if(q!==m){location.href=location.href.replace(/#.*/,"")+q}}p=setTimeout(n,$.fn[c].delay)}$.browser.msie&&!d&&(function(){var q,r;j.start=function(){if(!q){r=$.fn[c].src;r=r&&r+a();q=$(' + + +
+
+
mimalloc-doc.h
+
+
+
1 /* ----------------------------------------------------------------------------
2 Copyright (c) 2018, Microsoft Research, Daan Leijen
3 This is free software; you can redistribute it and/or modify it under the
4 terms of the MIT license. A copy of the license can be found in the file
5 "LICENSE" at the root of this distribution.
6 -----------------------------------------------------------------------------*/
7 
8 #error "documentation file only!"
9 
10 
83 
87 
91 void mi_free(void* p);
92 
97 void* mi_malloc(size_t size);
98 
103 void* mi_zalloc(size_t size);
104 
114 void* mi_calloc(size_t count, size_t size);
115 
128 void* mi_realloc(void* p, size_t newsize);
129 
140 void* mi_recalloc(void* p, size_t count, size_t size);
141 
155 void* mi_expand(void* p, size_t newsize);
156 
166 void* mi_mallocn(size_t count, size_t size);
167 
177 void* mi_reallocn(void* p, size_t count, size_t size);
178 
195 void* mi_reallocf(void* p, size_t newsize);
196 
197 
206 char* mi_strdup(const char* s);
207 
217 char* mi_strndup(const char* s, size_t n);
218 
231 char* mi_realpath(const char* fname, char* resolved_name);
232 
234 
235 // ------------------------------------------------------
236 // Extended functionality
237 // ------------------------------------------------------
238 
242 
245 #define MI_SMALL_SIZE_MAX (128*sizeof(void*))
246 
254 void* mi_malloc_small(size_t size);
255 
263 void* mi_zalloc_small(size_t size);
264 
279 size_t mi_usable_size(void* p);
280 
290 size_t mi_good_size(size_t size);
291 
299 void mi_collect(bool force);
300 
305 void mi_stats_print(void* out);
306 
312 void mi_stats_print_out(mi_output_fun* out, void* arg);
313 
315 void mi_stats_reset(void);
316 
318 void mi_stats_merge(void);
319 
323 void mi_thread_init(void);
324 
329 void mi_thread_done(void);
330 
336 void mi_thread_stats_print_out(mi_output_fun* out, void* arg);
337 
344 typedef void (mi_deferred_free_fun)(bool force, unsigned long long heartbeat, void* arg);
345 
361 void mi_register_deferred_free(mi_deferred_free_fun* deferred_free, void* arg);
362 
368 typedef void (mi_output_fun)(const char* msg, void* arg);
369 
376 void mi_register_output(mi_output_fun* out, void* arg);
377 
383 typedef void (mi_error_fun)(int err, void* arg);
384 
400 void mi_register_error(mi_error_fun* errfun, void* arg);
401 
406 bool mi_is_in_heap_region(const void* p);
407 
408 
421 int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs);
422 
435 int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs);
436 
437 
442 bool mi_is_redirected();
443 
444 
446 
447 // ------------------------------------------------------
448 // Aligned allocation
449 // ------------------------------------------------------
450 
456 
469 void* mi_malloc_aligned(size_t size, size_t alignment);
470 void* mi_zalloc_aligned(size_t size, size_t alignment);
471 void* mi_calloc_aligned(size_t count, size_t size, size_t alignment);
472 void* mi_realloc_aligned(void* p, size_t newsize, size_t alignment);
473 
484 void* mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset);
485 void* mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset);
486 void* mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset);
487 void* mi_realloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset);
488 
490 
496 
501 struct mi_heap_s;
502 
507 typedef struct mi_heap_s mi_heap_t;
508 
511 
519 void mi_heap_delete(mi_heap_t* heap);
520 
528 void mi_heap_destroy(mi_heap_t* heap);
529 
534 
538 
545 
547 void mi_heap_collect(mi_heap_t* heap, bool force);
548 
551 void* mi_heap_malloc(mi_heap_t* heap, size_t size);
552 
556 void* mi_heap_malloc_small(mi_heap_t* heap, size_t size);
557 
560 void* mi_heap_zalloc(mi_heap_t* heap, size_t size);
561 
564 void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size);
565 
568 void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size);
569 
572 char* mi_heap_strdup(mi_heap_t* heap, const char* s);
573 
576 char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n);
577 
580 char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name);
581 
582 void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize);
583 void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size);
584 void* mi_heap_reallocf(mi_heap_t* heap, void* p, size_t newsize);
585 
586 void* mi_heap_malloc_aligned(mi_heap_t* heap, size_t size, size_t alignment);
587 void* mi_heap_malloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset);
588 void* mi_heap_zalloc_aligned(mi_heap_t* heap, size_t size, size_t alignment);
589 void* mi_heap_zalloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset);
590 void* mi_heap_calloc_aligned(mi_heap_t* heap, size_t count, size_t size, size_t alignment);
591 void* mi_heap_calloc_aligned_at(mi_heap_t* heap, size_t count, size_t size, size_t alignment, size_t offset);
592 void* mi_heap_realloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment);
593 void* mi_heap_realloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset);
594 
596 
597 
606 
607 void* mi_rezalloc(void* p, size_t newsize);
608 void* mi_recalloc(void* p, size_t newcount, size_t size) ;
609 
610 void* mi_rezalloc_aligned(void* p, size_t newsize, size_t alignment);
611 void* mi_rezalloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset);
612 void* mi_recalloc_aligned(void* p, size_t newcount, size_t size, size_t alignment);
613 void* mi_recalloc_aligned_at(void* p, size_t newcount, size_t size, size_t alignment, size_t offset);
614 
615 void* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize);
616 void* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t newcount, size_t size);
617 
618 void* mi_heap_rezalloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment);
619 void* mi_heap_rezalloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset);
620 void* mi_heap_recalloc_aligned(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment);
621 void* mi_heap_recalloc_aligned_at(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment, size_t offset);
622 
624 
633 
645 #define mi_malloc_tp(tp) ((tp*)mi_malloc(sizeof(tp)))
646 
648 #define mi_zalloc_tp(tp) ((tp*)mi_zalloc(sizeof(tp)))
649 
651 #define mi_calloc_tp(tp,count) ((tp*)mi_calloc(count,sizeof(tp)))
652 
654 #define mi_mallocn_tp(tp,count) ((tp*)mi_mallocn(count,sizeof(tp)))
655 
657 #define mi_reallocn_tp(p,tp,count) ((tp*)mi_reallocn(p,count,sizeof(tp)))
658 
660 #define mi_heap_malloc_tp(hp,tp) ((tp*)mi_heap_malloc(hp,sizeof(tp)))
661 
663 #define mi_heap_zalloc_tp(hp,tp) ((tp*)mi_heap_zalloc(hp,sizeof(tp)))
664 
666 #define mi_heap_calloc_tp(hp,tp,count) ((tp*)mi_heap_calloc(hp,count,sizeof(tp)))
667 
669 #define mi_heap_mallocn_tp(hp,tp,count) ((tp*)mi_heap_mallocn(hp,count,sizeof(tp)))
670 
672 #define mi_heap_reallocn_tp(hp,p,tp,count) ((tp*)mi_heap_reallocn(p,count,sizeof(tp)))
673 
675 #define mi_heap_recalloc_tp(hp,p,tp,count) ((tp*)mi_heap_recalloc(p,count,sizeof(tp)))
676 
678 
684 
691 bool mi_heap_contains_block(mi_heap_t* heap, const void* p);
692 
701 bool mi_heap_check_owned(mi_heap_t* heap, const void* p);
702 
710 bool mi_check_owned(const void* p);
711 
714 typedef struct mi_heap_area_s {
715  void* blocks;
716  size_t reserved;
717  size_t committed;
718  size_t used;
719  size_t block_size;
721 
729 typedef bool (mi_block_visit_fun)(const mi_heap_t* heap, const mi_heap_area_t* area, void* block, size_t block_size, void* arg);
730 
742 bool mi_heap_visit_blocks(const mi_heap_t* heap, bool visit_all_blocks, mi_block_visit_fun* visitor, void* arg);
743 
745 
751 
753 typedef enum mi_option_e {
754  // stable options
758  // the following options are experimental
772 } mi_option_t;
773 
774 
775 bool mi_option_is_enabled(mi_option_t option);
776 void mi_option_enable(mi_option_t option);
777 void mi_option_disable(mi_option_t option);
778 void mi_option_set_enabled(mi_option_t option, bool enable);
779 void mi_option_set_enabled_default(mi_option_t option, bool enable);
780 
781 long mi_option_get(mi_option_t option);
782 void mi_option_set(mi_option_t option, long value);
783 void mi_option_set_default(mi_option_t option, long value);
784 
785 
787 
794 
795 void* mi_recalloc(void* p, size_t count, size_t size);
796 size_t mi_malloc_size(const void* p);
797 size_t mi_malloc_usable_size(const void *p);
798 
800 void mi_cfree(void* p);
801 
802 int mi_posix_memalign(void** p, size_t alignment, size_t size);
803 int mi__posix_memalign(void** p, size_t alignment, size_t size);
804 void* mi_memalign(size_t alignment, size_t size);
805 void* mi_valloc(size_t size);
806 
807 void* mi_pvalloc(size_t size);
808 void* mi_aligned_alloc(size_t alignment, size_t size);
809 void* mi_reallocarray(void* p, size_t count, size_t size);
810 
811 void mi_free_size(void* p, size_t size);
812 void mi_free_size_aligned(void* p, size_t size, size_t alignment);
813 void mi_free_aligned(void* p, size_t alignment);
814 
816 
829 
831 void* mi_new(std::size_t n) noexcept(false);
832 
834 void* mi_new_n(size_t count, size_t size) noexcept(false);
835 
837 void* mi_new_aligned(std::size_t n, std::align_val_t alignment) noexcept(false);
838 
840 void* mi_new_nothrow(size_t n);
841 
843 void* mi_new_aligned_nothrow(size_t n, size_t alignment);
844 
846 void* mi_new_realloc(void* p, size_t newsize);
847 
849 void* mi_new_reallocn(void* p, size_t newcount, size_t size);
850 
858 template<class T> struct mi_stl_allocator { }
859 
861 
size_t mi_usable_size(void *p)
Return the available bytes in a memory block.
+
void * mi_new_nothrow(size_t n)
like mi_malloc, but when out of memory, use std::get_new_handler but return NULL on failure.
+
void * mi_reallocn(void *p, size_t count, size_t size)
Re-allocate memory to count elements of size bytes.
+
void * mi_malloc_aligned(size_t size, size_t alignment)
Allocate size bytes aligned by alignment.
+
void * mi_recalloc_aligned_at(void *p, size_t newcount, size_t size, size_t alignment, size_t offset)
+
void mi_stats_reset(void)
Reset statistics.
+
void * mi_heap_realloc_aligned(mi_heap_t *heap, void *p, size_t newsize, size_t alignment)
+
bool mi_option_is_enabled(mi_option_t option)
+
void * mi_new_realloc(void *p, size_t newsize)
like mi_realloc(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exceptio...
+
void * mi_recalloc(void *p, size_t count, size_t size)
Re-allocate memory to count elements of size bytes, with extra memory initialized to zero.
+
void * mi_mallocn(size_t count, size_t size)
Allocate count elements of size bytes.
+
size_t mi_malloc_size(const void *p)
+
void mi_option_set_enabled(mi_option_t option, bool enable)
+
int mi_posix_memalign(void **p, size_t alignment, size_t size)
+
void mi_stats_merge(void)
Merge thread local statistics with the main statistics and reset.
+
void * mi_new_n(size_t count, size_t size) noexcept(false)
like mi_mallocn(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exceptio...
+
void mi_option_set_default(mi_option_t option, long value)
+
void mi_stats_print_out(mi_output_fun *out, void *arg)
Print the main statistics.
+
void() mi_error_fun(int err, void *arg)
Type of error callback functions.
Definition: mimalloc-doc.h:383
+
void * mi_rezalloc(void *p, size_t newsize)
+
Eagerly commit segments (4MiB) (enabled by default).
Definition: mimalloc-doc.h:759
+
void * mi_heap_zalloc(mi_heap_t *heap, size_t size)
Allocate zero-initialized in a specific heap.
+
void mi_option_set(mi_option_t option, long value)
+
Eagerly commit large (256MiB) memory regions (enabled by default, except on Windows)
Definition: mimalloc-doc.h:760
+
void mi_cfree(void *p)
Just as free but also checks if the pointer p belongs to our heap.
+
void * mi_recalloc_aligned(void *p, size_t newcount, size_t size, size_t alignment)
+
Definition: mimalloc-doc.h:771
+
void * mi_realloc_aligned_at(void *p, size_t newsize, size_t alignment, size_t offset)
+
void * blocks
start of the area containing heap blocks
Definition: mimalloc-doc.h:715
+
void * mi_realloc_aligned(void *p, size_t newsize, size_t alignment)
+
void mi_option_enable(mi_option_t option)
+
int mi__posix_memalign(void **p, size_t alignment, size_t size)
+
void mi_free(void *p)
Free previously allocated memory.
+
char * mi_heap_strdup(mi_heap_t *heap, const char *s)
Duplicate a string in a specific heap.
+
char * mi_heap_realpath(mi_heap_t *heap, const char *fname, char *resolved_name)
Resolve a file path name using a specific heap to allocate the result.
+
void * mi_heap_calloc_aligned_at(mi_heap_t *heap, size_t count, size_t size, size_t alignment, size_t offset)
+
void * mi_calloc_aligned(size_t count, size_t size, size_t alignment)
+
void * mi_heap_zalloc_aligned(mi_heap_t *heap, size_t size, size_t alignment)
+
void * mi_zalloc_small(size_t size)
Allocate a zero initialized small object.
+
char * mi_strndup(const char *s, size_t n)
Allocate and duplicate a string up to n bytes.
+
void * mi_expand(void *p, size_t newsize)
Try to re-allocate memory to newsize bytes in place.
+
void * mi_pvalloc(size_t size)
+
void mi_option_set_enabled_default(mi_option_t option, bool enable)
+
void * mi_heap_rezalloc_aligned_at(mi_heap_t *heap, void *p, size_t newsize, size_t alignment, size_t offset)
+
void * mi_zalloc(size_t size)
Allocate zero-initialized size bytes.
+
void * mi_heap_rezalloc(mi_heap_t *heap, void *p, size_t newsize)
+
The number of segments per thread to keep cached.
Definition: mimalloc-doc.h:763
+
void * mi_heap_calloc(mi_heap_t *heap, size_t count, size_t size)
Allocate count zero-initialized elements in a specific heap.
+
void * mi_heap_calloc_aligned(mi_heap_t *heap, size_t count, size_t size, size_t alignment)
+
bool mi_is_redirected()
Is the C runtime malloc API redirected?
+
size_t block_size
size in bytes of one block
Definition: mimalloc-doc.h:719
+
void * mi_reallocarray(void *p, size_t count, size_t size)
+
int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs)
Reserve pages of huge OS pages (1GiB) evenly divided over numa_nodes nodes, but stops after at most t...
+
void() mi_deferred_free_fun(bool force, unsigned long long heartbeat, void *arg)
Type of deferred free functions.
Definition: mimalloc-doc.h:344
+
bool mi_is_in_heap_region(const void *p)
Is a pointer part of our heap?
+
void * mi_new_aligned(std::size_t n, std::align_val_t alignment) noexcept(false)
like mi_malloc_aligned(), but when out of memory, use std::get_new_handler and raise std::bad_alloc e...
+
void * mi_realloc(void *p, size_t newsize)
Re-allocate memory to newsize bytes.
+
The number of huge OS pages (1GiB in size) to reserve at the start of the program.
Definition: mimalloc-doc.h:762
+
void * mi_heap_reallocf(mi_heap_t *heap, void *p, size_t newsize)
+
void mi_free_size_aligned(void *p, size_t size, size_t alignment)
+
void * mi_rezalloc_aligned_at(void *p, size_t newsize, size_t alignment, size_t offset)
+
Reset page memory after mi_option_reset_delay milliseconds when it becomes free.
Definition: mimalloc-doc.h:764
+
void mi_thread_done(void)
Uninitialize mimalloc on a thread.
+
bool mi_heap_visit_blocks(const mi_heap_t *heap, bool visit_all_blocks, mi_block_visit_fun *visitor, void *arg)
Visit all areas and blocks in a heap.
+
Pretend there are at most N NUMA nodes.
Definition: mimalloc-doc.h:767
+
void * mi_malloc(size_t size)
Allocate size bytes.
+
void mi_register_error(mi_error_fun *errfun, void *arg)
Register an error callback function.
+
Experimental.
Definition: mimalloc-doc.h:768
+
char * mi_heap_strndup(mi_heap_t *heap, const char *s, size_t n)
Duplicate a string of at most length n in a specific heap.
+
bool() mi_block_visit_fun(const mi_heap_t *heap, const mi_heap_area_t *area, void *block, size_t block_size, void *arg)
Visitor function passed to mi_heap_visit_blocks()
Definition: mimalloc-doc.h:729
+
void * mi_heap_recalloc(mi_heap_t *heap, void *p, size_t newcount, size_t size)
+
void * mi_heap_malloc_aligned_at(mi_heap_t *heap, size_t size, size_t alignment, size_t offset)
+
char * mi_realpath(const char *fname, char *resolved_name)
Resolve a file path name.
+
Print error messages to stderr.
Definition: mimalloc-doc.h:755
+
Experimental.
Definition: mimalloc-doc.h:765
+
void * mi_heap_rezalloc_aligned(mi_heap_t *heap, void *p, size_t newsize, size_t alignment)
+
void * mi_new_aligned_nothrow(size_t n, size_t alignment)
like mi_malloc_aligned, but when out of memory, use std::get_new_handler but return NULL on failure.
+
void * mi_memalign(size_t alignment, size_t size)
+
void * mi_rezalloc_aligned(void *p, size_t newsize, size_t alignment)
+
bool mi_heap_contains_block(mi_heap_t *heap, const void *p)
Does a heap contain a pointer to a previously allocated block?
+
void mi_heap_collect(mi_heap_t *heap, bool force)
Release outstanding resources in a specific heap.
+
void * mi_heap_recalloc_aligned_at(mi_heap_t *heap, void *p, size_t newcount, size_t size, size_t alignment, size_t offset)
+
Print verbose messages to stderr.
Definition: mimalloc-doc.h:757
+
void * mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset)
+
void * mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset)
Allocate size bytes aligned by alignment at a specified offset.
+
void mi_heap_delete(mi_heap_t *heap)
Delete a previously allocated heap.
+
OS tag to assign to mimalloc'd memory.
Definition: mimalloc-doc.h:770
+
mi_heap_t * mi_heap_get_default()
Get the default heap that is used for mi_malloc() et al.
+
int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs)
Reserve pages of huge OS pages (1GiB) at a specific numa_node, but stops after at most timeout_msecs ...
+
void mi_option_disable(mi_option_t option)
+
void * mi_aligned_alloc(size_t alignment, size_t size)
+
void * mi_valloc(size_t size)
+
void mi_thread_init(void)
Initialize mimalloc on a thread.
+
size_t mi_good_size(size_t size)
Return the used allocation size.
+
void mi_stats_print(void *out)
Deprecated.
+
Experimental.
Definition: mimalloc-doc.h:769
+
void * mi_heap_recalloc_aligned(mi_heap_t *heap, void *p, size_t newcount, size_t size, size_t alignment)
+
void * mi_heap_mallocn(mi_heap_t *heap, size_t count, size_t size)
Allocate count elements in a specific heap.
+
An area of heap space contains blocks of a single size.
Definition: mimalloc-doc.h:714
+
void mi_thread_stats_print_out(mi_output_fun *out, void *arg)
Print out heap statistics for this thread.
+
Print statistics to stderr when the program is done.
Definition: mimalloc-doc.h:756
+
void * mi_zalloc_aligned(size_t size, size_t alignment)
+
size_t reserved
bytes reserved for this area
Definition: mimalloc-doc.h:716
+
struct mi_heap_s mi_heap_t
Type of first-class heaps.
Definition: mimalloc-doc.h:507
+
size_t used
bytes in use by allocated blocks
Definition: mimalloc-doc.h:718
+
void mi_register_deferred_free(mi_deferred_free_fun *deferred_free, void *arg)
Register a deferred free function.
+
void mi_free_size(void *p, size_t size)
+
void mi_collect(bool force)
Eagerly free memory.
+
void * mi_new_reallocn(void *p, size_t newcount, size_t size)
like mi_reallocn(), but when out of memory, use std::get_new_handler and raise std::bad_alloc excepti...
+
void mi_heap_destroy(mi_heap_t *heap)
Destroy a heap, freeing all its still allocated blocks.
+
void * mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset)
+
Use large OS pages (2MiB in size) if possible.
Definition: mimalloc-doc.h:761
+
void * mi_heap_reallocn(mi_heap_t *heap, void *p, size_t count, size_t size)
+
void mi_register_output(mi_output_fun *out, void *arg)
Register an output function.
+
std::allocator implementation for mimalloc for use in STL containers.
Definition: mimalloc-doc.h:858
+
void * mi_heap_malloc_small(mi_heap_t *heap, size_t size)
Allocate a small object in a specific heap.
+
void * mi_heap_realloc(mi_heap_t *heap, void *p, size_t newsize)
+
size_t mi_malloc_usable_size(const void *p)
+
void() mi_output_fun(const char *msg, void *arg)
Type of output functions.
Definition: mimalloc-doc.h:368
+
char * mi_strdup(const char *s)
Allocate and duplicate a string.
+
void * mi_heap_realloc_aligned_at(mi_heap_t *heap, void *p, size_t newsize, size_t alignment, size_t offset)
+
void * mi_reallocf(void *p, size_t newsize)
Re-allocate memory to newsize bytes,.
+
void * mi_calloc(size_t count, size_t size)
Allocate zero-initialized count elements of size bytes.
+
void * mi_heap_zalloc_aligned_at(mi_heap_t *heap, size_t size, size_t alignment, size_t offset)
+
void * mi_malloc_small(size_t size)
Allocate a small object.
+
bool mi_check_owned(const void *p)
Check safely if any pointer is part of the default heap of this thread.
+
void * mi_heap_malloc_aligned(mi_heap_t *heap, size_t size, size_t alignment)
+
long mi_option_get(mi_option_t option)
+
mi_heap_t * mi_heap_get_backing()
Get the backing heap.
+
void mi_free_aligned(void *p, size_t alignment)
+
void * mi_new(std::size_t n) noexcept(false)
like mi_malloc(), but when out of memory, use std::get_new_handler and raise std::bad_alloc exception...
+
Delay in milli-seconds before resetting a page (100ms by default)
Definition: mimalloc-doc.h:766
+
mi_heap_t * mi_heap_new()
Create a new heap that can be used for allocation.
+
void * mi_heap_malloc(mi_heap_t *heap, size_t size)
Allocate in a specific heap.
+
size_t committed
current committed bytes of this area
Definition: mimalloc-doc.h:717
+
mi_option_t
Runtime options.
Definition: mimalloc-doc.h:753
+
bool mi_heap_check_owned(mi_heap_t *heap, const void *p)
Check safely if any pointer is part of a heap.
+
mi_heap_t * mi_heap_set_default(mi_heap_t *heap)
Set the default heap to use for mi_malloc() et al.
+
+ + + + + diff --git a/extlib/mimalloc/docs/mimalloc-doxygen.css b/extlib/mimalloc/docs/mimalloc-doxygen.css new file mode 100644 index 0000000..b24f564 --- /dev/null +++ b/extlib/mimalloc/docs/mimalloc-doxygen.css @@ -0,0 +1,49 @@ +#projectlogo img { + padding: 1ex; +} +tt, code, kbd, samp, div.memproto, div.fragment, div.line, table.memname { + font-family: Consolas, Monaco, Inconsolata, "Courier New", monospace; +} +.image img, .textblock img { + max-width: 99%; + max-height: 350px; +} +table.memname, .memname{ + font-weight: bold; +} +code { + background-color: #EEE; + padding: 0ex 0.25ex; +} +body { + margin: 1ex 1ex 0ex 1ex; + border: 1px solid black; +} +.contents table, .contents div, .contents p, .contents dl { + font-size: 16px; + line-height: 1.44; +} +body #nav-tree .label { + font-size: 14px; +} +a{ + text-decoration: underline; +} +#side-nav { + margin-left: 1ex; + border-left: 1px solid black; +} +#nav-tree { + padding-left: 1ex; +} +#nav-path { + display: none; +} +div.fragment { + background-color: #EEE; + padding: 0.25ex 0.5ex; + border-color: black; +} +#nav-sync img { + display: none; +} diff --git a/extlib/mimalloc/docs/mimalloc-logo.svg b/extlib/mimalloc/docs/mimalloc-logo.svg new file mode 100644 index 0000000..672c7e4 --- /dev/null +++ b/extlib/mimalloc/docs/mimalloc-logo.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extlib/mimalloc/docs/modules.html b/extlib/mimalloc/docs/modules.html new file mode 100644 index 0000000..9858d6a --- /dev/null +++ b/extlib/mimalloc/docs/modules.html @@ -0,0 +1,130 @@ + + + + + + + +mi-malloc: Modules + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+ + + + + +
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
Modules
+
+
+
Here is a list of all modules:
+ + + + + + + + + + + +
 Basic AllocationThe basic allocation interface
 Extended FunctionsExtended functionality
 Aligned AllocationAllocating aligned memory blocks
 Heap AllocationFirst-class heaps that can be destroyed in one go
 Zero initialized re-allocationThe zero-initialized re-allocations are only valid on memory that was originally allocated with zero initialization too
 Typed MacrosTyped allocation macros
 Heap IntrospectionInspect the heap at runtime
 Runtime OptionsSet runtime behavior
 Posixmi_ prefixed implementations of various Posix, Unix, and C++ allocation functions
 C++ wrappersmi_ prefixed implementations of various allocation functions that use C++ semantics on out-of-memory, generally calling std::get_new_handler and raising a std::bad_alloc exception on failure
+
+
+
+ + + + diff --git a/extlib/mimalloc/docs/modules.js b/extlib/mimalloc/docs/modules.js new file mode 100644 index 0000000..b2c2a22 --- /dev/null +++ b/extlib/mimalloc/docs/modules.js @@ -0,0 +1,13 @@ +var modules = +[ + [ "Basic Allocation", "group__malloc.html", "group__malloc" ], + [ "Extended Functions", "group__extended.html", "group__extended" ], + [ "Aligned Allocation", "group__aligned.html", "group__aligned" ], + [ "Heap Allocation", "group__heap.html", "group__heap" ], + [ "Zero initialized re-allocation", "group__zeroinit.html", "group__zeroinit" ], + [ "Typed Macros", "group__typed.html", "group__typed" ], + [ "Heap Introspection", "group__analysis.html", "group__analysis" ], + [ "Runtime Options", "group__options.html", "group__options" ], + [ "Posix", "group__posix.html", "group__posix" ], + [ "C++ wrappers", "group__cpp.html", "group__cpp" ] +]; \ No newline at end of file diff --git a/extlib/mimalloc/docs/nav_f.png b/extlib/mimalloc/docs/nav_f.png new file mode 100644 index 0000000000000000000000000000000000000000..cbfef42971c02cbb567d442288beaea7846aed80 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^j6iI`!2~2XGqLUlQiYx_jv*C{Z|6GlH5rJw?B8R& z=|b*)wz1~t?r17iS5u2in6$mZxR|5zo%QW+3)X1w6bU+aWJ+FXFjDnS*rqE8e;9c50s88ocGXx3W_G$8#6`dpl%STs?0u|8H+-+^+=L Vy4zM_9ze?(JYD@<);T3K0RSUpLL&eG literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/nav_g.png b/extlib/mimalloc/docs/nav_g.png new file mode 100644 index 0000000000000000000000000000000000000000..2093a237a94f6c83e19ec6e5fd42f7ddabdafa81 GIT binary patch literal 95 zcmeAS@N?(olHy`uVBq!ia0vp^j6lrB!3HFm1ilyoDK$?Q$B+ufw|5PB85lU25BhtE tr?otc=hd~V+ws&_A@j8Fiv!KF$B+ufw|5WnGB|KBIWnil vE&3bY=ytO7VD=3yC!u&Tj{FaMzc)oQ@8@PI$gua?2h!!~>gTe~DWM4fIzAf1 literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/navtree.css b/extlib/mimalloc/docs/navtree.css new file mode 100644 index 0000000..046366c --- /dev/null +++ b/extlib/mimalloc/docs/navtree.css @@ -0,0 +1,146 @@ +#nav-tree .children_ul { + margin:0; + padding:4px; +} + +#nav-tree ul { + list-style:none outside none; + margin:0px; + padding:0px; +} + +#nav-tree li { + white-space:nowrap; + margin:0px; + padding:0px; +} + +#nav-tree .plus { + margin:0px; +} + +#nav-tree .selected { + background-image: url('tab_a.png'); + background-repeat:repeat-x; + color: #fff; + text-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +} + +#nav-tree img { + margin:0px; + padding:0px; + border:0px; + vertical-align: middle; +} + +#nav-tree a { + text-decoration:none; + padding:0px; + margin:0px; + outline:none; +} + +#nav-tree .label { + margin:0px; + padding:0px; + font: 12px 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +} + +#nav-tree .label a { + padding:2px; +} + +#nav-tree .selected a { + text-decoration:none; + color:#fff; +} + +#nav-tree .children_ul { + margin:0px; + padding:0px; +} + +#nav-tree .item { + margin:0px; + padding:0px; +} + +#nav-tree { + padding: 0px 0px; + background-color: #FAFAFF; + font-size:14px; + overflow:auto; +} + +#doc-content { + overflow:auto; + display:block; + padding:0px; + margin:0px; + -webkit-overflow-scrolling : touch; /* iOS 5+ */ +} + +#side-nav { + padding:0 6px 0 0; + margin: 0px; + display:block; + position: absolute; + left: 0px; + width: 180px; +} + +.ui-resizable .ui-resizable-handle { + display:block; +} + +.ui-resizable-e { + background-image:url("splitbar.png"); + background-size:100%; + background-repeat:repeat-y; + background-attachment: scroll; + cursor:ew-resize; + height:100%; + right:0; + top:0; + width:6px; +} + +.ui-resizable-handle { + display:none; + font-size:0.1px; + position:absolute; + z-index:1; +} + +#nav-tree-contents { + margin: 6px 0px 0px 0px; +} + +#nav-tree { + background-image:url('nav_h.png'); + background-repeat:repeat-x; + background-color: #F2F3F3; + -webkit-overflow-scrolling : touch; /* iOS 5+ */ +} + +#nav-sync { + position:absolute; + top:5px; + right:24px; + z-index:0; +} + +#nav-sync img { + opacity:0.3; +} + +#nav-sync img:hover { + opacity:0.9; +} + +@media print +{ + #nav-tree { display: none; } + div.ui-resizable-handle { display: none; position: relative; } +} + diff --git a/extlib/mimalloc/docs/navtree.js b/extlib/mimalloc/docs/navtree.js new file mode 100644 index 0000000..7ce2935 --- /dev/null +++ b/extlib/mimalloc/docs/navtree.js @@ -0,0 +1,540 @@ +/* + @licstart The following is the entire license notice for the + JavaScript code in this file. + + Copyright (C) 1997-2017 by Dimitri van Heesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + @licend The above is the entire license notice + for the JavaScript code in this file + */ +var navTreeSubIndices = new Array(); +var arrowDown = '▼'; +var arrowRight = '►'; + +function getData(varName) +{ + var i = varName.lastIndexOf('/'); + var n = i>=0 ? varName.substring(i+1) : varName; + return eval(n.replace(/\-/g,'_')); +} + +function stripPath(uri) +{ + return uri.substring(uri.lastIndexOf('/')+1); +} + +function stripPath2(uri) +{ + var i = uri.lastIndexOf('/'); + var s = uri.substring(i+1); + var m = uri.substring(0,i+1).match(/\/d\w\/d\w\w\/$/); + return m ? uri.substring(i-6) : s; +} + +function hashValue() +{ + return $(location).attr('hash').substring(1).replace(/[^\w\-]/g,''); +} + +function hashUrl() +{ + return '#'+hashValue(); +} + +function pathName() +{ + return $(location).attr('pathname').replace(/[^-A-Za-z0-9+&@#/%?=~_|!:,.;\(\)]/g, ''); +} + +function localStorageSupported() +{ + try { + return 'localStorage' in window && window['localStorage'] !== null && window.localStorage.getItem; + } + catch(e) { + return false; + } +} + + +function storeLink(link) +{ + if (!$("#nav-sync").hasClass('sync') && localStorageSupported()) { + window.localStorage.setItem('navpath',link); + } +} + +function deleteLink() +{ + if (localStorageSupported()) { + window.localStorage.setItem('navpath',''); + } +} + +function cachedLink() +{ + if (localStorageSupported()) { + return window.localStorage.getItem('navpath'); + } else { + return ''; + } +} + +function getScript(scriptName,func,show) +{ + var head = document.getElementsByTagName("head")[0]; + var script = document.createElement('script'); + script.id = scriptName; + script.type = 'text/javascript'; + script.onload = func; + script.src = scriptName+'.js'; + if ($.browser.msie && $.browser.version<=8) { + // script.onload does not work with older versions of IE + script.onreadystatechange = function() { + if (script.readyState=='complete' || script.readyState=='loaded') { + func(); if (show) showRoot(); + } + } + } + head.appendChild(script); +} + +function createIndent(o,domNode,node,level) +{ + var level=-1; + var n = node; + while (n.parentNode) { level++; n=n.parentNode; } + if (node.childrenData) { + var imgNode = document.createElement("span"); + imgNode.className = 'arrow'; + imgNode.style.paddingLeft=(16*level).toString()+'px'; + imgNode.innerHTML=arrowRight; + node.plus_img = imgNode; + node.expandToggle = document.createElement("a"); + node.expandToggle.href = "javascript:void(0)"; + node.expandToggle.onclick = function() { + if (node.expanded) { + $(node.getChildrenUL()).slideUp("fast"); + node.plus_img.innerHTML=arrowRight; + node.expanded = false; + } else { + expandNode(o, node, false, false); + } + } + node.expandToggle.appendChild(imgNode); + domNode.appendChild(node.expandToggle); + } else { + var span = document.createElement("span"); + span.className = 'arrow'; + span.style.width = 16*(level+1)+'px'; + span.innerHTML = ' '; + domNode.appendChild(span); + } +} + +var animationInProgress = false; + +function gotoAnchor(anchor,aname,updateLocation) +{ + var pos, docContent = $('#doc-content'); + var ancParent = $(anchor.parent()); + if (ancParent.hasClass('memItemLeft') || + ancParent.hasClass('fieldname') || + ancParent.hasClass('fieldtype') || + ancParent.is(':header')) + { + pos = ancParent.position().top; + } else if (anchor.position()) { + pos = anchor.position().top; + } + if (pos) { + var dist = Math.abs(Math.min( + pos-docContent.offset().top, + docContent[0].scrollHeight- + docContent.height()-docContent.scrollTop())); + animationInProgress=true; + docContent.animate({ + scrollTop: pos + docContent.scrollTop() - docContent.offset().top + },Math.max(50,Math.min(500,dist)),function(){ + if (updateLocation) window.location.href=aname; + animationInProgress=false; + }); + } +} + +function newNode(o, po, text, link, childrenData, lastNode) +{ + var node = new Object(); + node.children = Array(); + node.childrenData = childrenData; + node.depth = po.depth + 1; + node.relpath = po.relpath; + node.isLast = lastNode; + + node.li = document.createElement("li"); + po.getChildrenUL().appendChild(node.li); + node.parentNode = po; + + node.itemDiv = document.createElement("div"); + node.itemDiv.className = "item"; + + node.labelSpan = document.createElement("span"); + node.labelSpan.className = "label"; + + createIndent(o,node.itemDiv,node,0); + node.itemDiv.appendChild(node.labelSpan); + node.li.appendChild(node.itemDiv); + + var a = document.createElement("a"); + node.labelSpan.appendChild(a); + node.label = document.createTextNode(text); + node.expanded = false; + a.appendChild(node.label); + if (link) { + var url; + if (link.substring(0,1)=='^') { + url = link.substring(1); + link = url; + } else { + url = node.relpath+link; + } + a.className = stripPath(link.replace('#',':')); + if (link.indexOf('#')!=-1) { + var aname = '#'+link.split('#')[1]; + var srcPage = stripPath(pathName()); + var targetPage = stripPath(link.split('#')[0]); + a.href = srcPage!=targetPage ? url : "javascript:void(0)"; + a.onclick = function(){ + storeLink(link); + if (!$(a).parent().parent().hasClass('selected')) + { + $('.item').removeClass('selected'); + $('.item').removeAttr('id'); + $(a).parent().parent().addClass('selected'); + $(a).parent().parent().attr('id','selected'); + } + var anchor = $(aname); + gotoAnchor(anchor,aname,true); + }; + } else { + a.href = url; + a.onclick = function() { storeLink(link); } + } + } else { + if (childrenData != null) + { + a.className = "nolink"; + a.href = "javascript:void(0)"; + a.onclick = node.expandToggle.onclick; + } + } + + node.childrenUL = null; + node.getChildrenUL = function() { + if (!node.childrenUL) { + node.childrenUL = document.createElement("ul"); + node.childrenUL.className = "children_ul"; + node.childrenUL.style.display = "none"; + node.li.appendChild(node.childrenUL); + } + return node.childrenUL; + }; + + return node; +} + +function showRoot() +{ + var headerHeight = $("#top").height(); + var footerHeight = $("#nav-path").height(); + var windowHeight = $(window).height() - headerHeight - footerHeight; + (function (){ // retry until we can scroll to the selected item + try { + var navtree=$('#nav-tree'); + navtree.scrollTo('#selected',0,{offset:-windowHeight/2}); + } catch (err) { + setTimeout(arguments.callee, 0); + } + })(); +} + +function expandNode(o, node, imm, showRoot) +{ + if (node.childrenData && !node.expanded) { + if (typeof(node.childrenData)==='string') { + var varName = node.childrenData; + getScript(node.relpath+varName,function(){ + node.childrenData = getData(varName); + expandNode(o, node, imm, showRoot); + }, showRoot); + } else { + if (!node.childrenVisited) { + getNode(o, node); + } if (imm || ($.browser.msie && $.browser.version>8)) { + // somehow slideDown jumps to the start of tree for IE9 :-( + $(node.getChildrenUL()).show(); + } else { + $(node.getChildrenUL()).slideDown("fast"); + } + node.plus_img.innerHTML = arrowDown; + node.expanded = true; + } + } +} + +function glowEffect(n,duration) +{ + n.addClass('glow').delay(duration).queue(function(next){ + $(this).removeClass('glow');next(); + }); +} + +function highlightAnchor() +{ + var aname = hashUrl(); + var anchor = $(aname); + if (anchor.parent().attr('class')=='memItemLeft'){ + var rows = $('.memberdecls tr[class$="'+hashValue()+'"]'); + glowEffect(rows.children(),300); // member without details + } else if (anchor.parent().attr('class')=='fieldname'){ + glowEffect(anchor.parent().parent(),1000); // enum value + } else if (anchor.parent().attr('class')=='fieldtype'){ + glowEffect(anchor.parent().parent(),1000); // struct field + } else if (anchor.parent().is(":header")) { + glowEffect(anchor.parent(),1000); // section header + } else { + glowEffect(anchor.next(),1000); // normal member + } + gotoAnchor(anchor,aname,false); +} + +function selectAndHighlight(hash,n) +{ + var a; + if (hash) { + var link=stripPath(pathName())+':'+hash.substring(1); + a=$('.item a[class$="'+link+'"]'); + } + if (a && a.length) { + a.parent().parent().addClass('selected'); + a.parent().parent().attr('id','selected'); + highlightAnchor(); + } else if (n) { + $(n.itemDiv).addClass('selected'); + $(n.itemDiv).attr('id','selected'); + } + if ($('#nav-tree-contents .item:first').hasClass('selected')) { + $('#nav-sync').css('top','30px'); + } else { + $('#nav-sync').css('top','5px'); + } + showRoot(); +} + +function showNode(o, node, index, hash) +{ + if (node && node.childrenData) { + if (typeof(node.childrenData)==='string') { + var varName = node.childrenData; + getScript(node.relpath+varName,function(){ + node.childrenData = getData(varName); + showNode(o,node,index,hash); + },true); + } else { + if (!node.childrenVisited) { + getNode(o, node); + } + $(node.getChildrenUL()).css({'display':'block'}); + node.plus_img.innerHTML = arrowDown; + node.expanded = true; + var n = node.children[o.breadcrumbs[index]]; + if (index+11) hash = '#'+parts[1].replace(/[^\w\-]/g,''); + else hash=''; + } + if (hash.match(/^#l\d+$/)) { + var anchor=$('a[name='+hash.substring(1)+']'); + glowEffect(anchor.parent(),1000); // line number + hash=''; // strip line number anchors + } + var url=root+hash; + var i=-1; + while (NAVTREEINDEX[i+1]<=url) i++; + if (i==-1) { i=0; root=NAVTREE[0][1]; } // fallback: show index + if (navTreeSubIndices[i]) { + gotoNode(o,i,root,hash,relpath) + } else { + getScript(relpath+'navtreeindex'+i,function(){ + navTreeSubIndices[i] = eval('NAVTREEINDEX'+i); + if (navTreeSubIndices[i]) { + gotoNode(o,i,root,hash,relpath); + } + },true); + } +} + +function showSyncOff(n,relpath) +{ + n.html(''); +} + +function showSyncOn(n,relpath) +{ + n.html(''); +} + +function toggleSyncButton(relpath) +{ + var navSync = $('#nav-sync'); + if (navSync.hasClass('sync')) { + navSync.removeClass('sync'); + showSyncOff(navSync,relpath); + storeLink(stripPath2(pathName())+hashUrl()); + } else { + navSync.addClass('sync'); + showSyncOn(navSync,relpath); + deleteLink(); + } +} + +function initNavTree(toroot,relpath) +{ + var o = new Object(); + o.toroot = toroot; + o.node = new Object(); + o.node.li = document.getElementById("nav-tree-contents"); + o.node.childrenData = NAVTREE; + o.node.children = new Array(); + o.node.childrenUL = document.createElement("ul"); + o.node.getChildrenUL = function() { return o.node.childrenUL; }; + o.node.li.appendChild(o.node.childrenUL); + o.node.depth = 0; + o.node.relpath = relpath; + o.node.expanded = false; + o.node.isLast = true; + o.node.plus_img = document.createElement("span"); + o.node.plus_img.className = 'arrow'; + o.node.plus_img.innerHTML = arrowRight; + + if (localStorageSupported()) { + var navSync = $('#nav-sync'); + if (cachedLink()) { + showSyncOff(navSync,relpath); + navSync.removeClass('sync'); + } else { + showSyncOn(navSync,relpath); + } + navSync.click(function(){ toggleSyncButton(relpath); }); + } + + $(window).load(function(){ + navTo(o,toroot,hashUrl(),relpath); + showRoot(); + }); + + $(window).bind('hashchange', function(){ + if (window.location.hash && window.location.hash.length>1){ + var a; + if ($(location).attr('hash')){ + var clslink=stripPath(pathName())+':'+hashValue(); + a=$('.item a[class$="'+clslink.replace(/1|%O$WD@{VHl8kyAr*{o=WgU=P~c(FUzfUg za`xkxQ@1&if^I(*`e$jxd>}tUuawDUMwm+9y&Ug(8jj|7TcHA QfaWlGy85}Sb4q9e0Q%!3%m4rY literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/overrides.html b/extlib/mimalloc/docs/overrides.html new file mode 100644 index 0000000..2a6c51e --- /dev/null +++ b/extlib/mimalloc/docs/overrides.html @@ -0,0 +1,142 @@ + + + + + + + +mi-malloc: Overriding Malloc + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
Overriding Malloc
+
+
+

Overriding the standard malloc can be done either dynamically or statically.

+

Dynamic override

+

This is the recommended way to override the standard malloc interface.

+

Linux, BSD

+

On these systems we preload the mimalloc shared library so all calls to the standard malloc interface are resolved to the mimalloc library.

+
    +
  • env LD_PRELOAD=/usr/lib/libmimalloc.so myprogram
  • +
+

You can set extra environment variables to check that mimalloc is running, like:

env MIMALLOC_VERBOSE=1 LD_PRELOAD=/usr/lib/libmimalloc.so myprogram

or run with the debug version to get detailed statistics:

env MIMALLOC_SHOW_STATS=1 LD_PRELOAD=/usr/lib/libmimalloc-debug.so myprogram

MacOS

+

On macOS we can also preload the mimalloc shared library so all calls to the standard malloc interface are resolved to the mimalloc library.

+
    +
  • env DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=/usr/lib/libmimalloc.dylib myprogram
  • +
+

Note that certain security restrictions may apply when doing this from the shell.

+

(Note: macOS support for dynamic overriding is recent, please report any issues.)

+

Windows

+

Overriding on Windows is robust and has the particular advantage to be able to redirect all malloc/free calls that go through the (dynamic) C runtime allocator, including those from other DLL's or libraries.

+

The overriding on Windows requires that you link your program explicitly with the mimalloc DLL and use the C-runtime library as a DLL (using the /MD or /MDd switch). Also, the mimalloc-redirect.dll (or mimalloc-redirect32.dll) must be available in the same folder as the main mimalloc-override.dll at runtime (as it is a dependency). The redirection DLL ensures that all calls to the C runtime malloc API get redirected to mimalloc (in mimalloc-override.dll).

+

To ensure the mimalloc DLL is loaded at run-time it is easiest to insert some call to the mimalloc API in the main function, like mi_version() (or use the /INCLUDE:mi_version switch on the linker). See the mimalloc-override-test project for an example on how to use this. For best performance on Windows with C++, it is also recommended to also override the new/delete operations (by including mimalloc-new-delete.h a single(!) source file in your project).

+

The environment variable MIMALLOC_DISABLE_REDIRECT=1 can be used to disable dynamic overriding at run-time. Use MIMALLOC_VERBOSE=1 to check if mimalloc was successfully redirected.

+

(Note: in principle, it is possible to even patch existing executables without any recompilation if they are linked with the dynamic C runtime (ucrtbase.dll) – just put the mimalloc-override.dll into the import table (and put mimalloc-redirect.dll in the same folder) Such patching can be done for example with CFF Explorer).

+

Static override

+

On Unix systems, you can also statically link with mimalloc to override the standard malloc interface. The recommended way is to link the final program with the mimalloc single object file (mimalloc-override.o). We use an object file instead of a library file as linkers give preference to that over archives to resolve symbols. To ensure that the standard malloc interface resolves to the mimalloc library, link it as the first object file. For example:

+
gcc -o myprogram mimalloc-override.o myfile1.c ...

List of Overrides:

+

The specific functions that get redirected to the mimalloc library are:

+
// C
void* malloc(size_t size);
void* calloc(size_t size, size_t n);
void* realloc(void* p, size_t newsize);
void free(void* p);
// C++
void operator delete(void* p);
void operator delete[](void* p);
void* operator new(std::size_t n) noexcept(false);
void* operator new[](std::size_t n) noexcept(false);
void* operator new( std::size_t n, std::align_val_t align) noexcept(false);
void* operator new[]( std::size_t n, std::align_val_t align) noexcept(false);
void* operator new ( std::size_t count, const std::nothrow_t& tag);
void* operator new[]( std::size_t count, const std::nothrow_t& tag);
void* operator new ( std::size_t count, std::align_val_t al, const std::nothrow_t&);
void* operator new[]( std::size_t count, std::align_val_t al, const std::nothrow_t&);
// Posix
int posix_memalign(void** p, size_t alignment, size_t size);
// Linux
void* memalign(size_t alignment, size_t size);
void* aligned_alloc(size_t alignment, size_t size);
void* valloc(size_t size);
void* pvalloc(size_t size);
size_t malloc_usable_size(void *p);
// BSD
void* reallocarray( void* p, size_t count, size_t size );
void* reallocf(void* p, size_t newsize);
void cfree(void* p);
// Windows
void* _expand(void* p, size_t newsize);
size_t _msize(void* p);
void* _malloc_dbg(size_t size, int block_type, const char* fname, int line);
void* _realloc_dbg(void* p, size_t newsize, int block_type, const char* fname, int line);
void* _calloc_dbg(size_t count, size_t size, int block_type, const char* fname, int line);
void* _expand_dbg(void* p, size_t size, int block_type, const char* fname, int line);
size_t _msize_dbg(void* p, int block_type);
void _free_dbg(void* p, int block_type);
+
+
+ + + + diff --git a/extlib/mimalloc/docs/pages.html b/extlib/mimalloc/docs/pages.html new file mode 100644 index 0000000..8fcd6f0 --- /dev/null +++ b/extlib/mimalloc/docs/pages.html @@ -0,0 +1,125 @@ + + + + + + + +mi-malloc: Related Pages + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
Related Pages
+
+
+
Here is a list of all related documentation pages:
+
+
+ + + + diff --git a/extlib/mimalloc/docs/resize.js b/extlib/mimalloc/docs/resize.js new file mode 100644 index 0000000..6617aee --- /dev/null +++ b/extlib/mimalloc/docs/resize.js @@ -0,0 +1,136 @@ +/* + @licstart The following is the entire license notice for the + JavaScript code in this file. + + Copyright (C) 1997-2017 by Dimitri van Heesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + @licend The above is the entire license notice + for the JavaScript code in this file + */ +function initResizable() +{ + var cookie_namespace = 'doxygen'; + var sidenav,navtree,content,header,collapsed,collapsedWidth=0,barWidth=6,desktop_vp=768,titleHeight; + + function readCookie(cookie) + { + var myCookie = cookie_namespace+"_"+cookie+"="; + if (document.cookie) { + var index = document.cookie.indexOf(myCookie); + if (index != -1) { + var valStart = index + myCookie.length; + var valEnd = document.cookie.indexOf(";", valStart); + if (valEnd == -1) { + valEnd = document.cookie.length; + } + var val = document.cookie.substring(valStart, valEnd); + return val; + } + } + return 0; + } + + function writeCookie(cookie, val, expiration) + { + if (val==undefined) return; + if (expiration == null) { + var date = new Date(); + date.setTime(date.getTime()+(10*365*24*60*60*1000)); // default expiration is one week + expiration = date.toGMTString(); + } + document.cookie = cookie_namespace + "_" + cookie + "=" + val + "; expires=" + expiration+"; path=/"; + } + + function resizeWidth() + { + var windowWidth = $(window).width() + "px"; + var sidenavWidth = $(sidenav).outerWidth(); + content.css({marginLeft:parseInt(sidenavWidth)+"px"}); + writeCookie('width',sidenavWidth-barWidth, null); + } + + function restoreWidth(navWidth) + { + var windowWidth = $(window).width() + "px"; + content.css({marginLeft:parseInt(navWidth)+barWidth+"px"}); + sidenav.css({width:navWidth + "px"}); + } + + function resizeHeight() + { + var headerHeight = header.outerHeight(); + var footerHeight = footer.outerHeight(); + var windowHeight = $(window).height() - headerHeight - footerHeight; + content.css({height:windowHeight + "px"}); + navtree.css({height:windowHeight + "px"}); + sidenav.css({height:windowHeight + "px"}); + var width=$(window).width(); + if (width!=collapsedWidth) { + if (width=desktop_vp) { + if (!collapsed) { + collapseExpand(); + } + } else if (width>desktop_vp && collapsedWidth0) { + restoreWidth(0); + collapsed=true; + } + else { + var width = readCookie('width'); + if (width>200 && width<$(window).width()) { restoreWidth(width); } else { restoreWidth(200); } + collapsed=false; + } + } + + header = $("#top"); + sidenav = $("#side-nav"); + content = $("#doc-content"); + navtree = $("#nav-tree"); + footer = $("#nav-path"); + $(".side-nav-resizable").resizable({resize: function(e, ui) { resizeWidth(); } }); + $(sidenav).resizable({ minWidth: 0 }); + $(window).resize(function() { resizeHeight(); }); + var device = navigator.userAgent.toLowerCase(); + var touch_device = device.match(/(iphone|ipod|ipad|android)/); + if (touch_device) { /* wider split bar for touch only devices */ + $(sidenav).css({ paddingRight:'20px' }); + $('.ui-resizable-e').css({ width:'20px' }); + $('#nav-sync').css({ right:'34px' }); + barWidth=20; + } + var width = readCookie('width'); + if (width) { restoreWidth(width); } else { resizeWidth(); } + resizeHeight(); + var url = location.href; + var i=url.indexOf("#"); + if (i>=0) window.location.hash=url.substr(i); + var _preventDefault = function(evt) { evt.preventDefault(); }; + $("#splitbar").bind("dragstart", _preventDefault).bind("selectstart", _preventDefault); + $(".ui-resizable-handle").dblclick(collapseExpand); + $(window).load(resizeHeight); +} +/* @license-end */ diff --git a/extlib/mimalloc/docs/search/all_0.html b/extlib/mimalloc/docs/search/all_0.html new file mode 100644 index 0000000..5330204 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_0.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_0.js b/extlib/mimalloc/docs/search/all_0.js new file mode 100644 index 0000000..7054b6d --- /dev/null +++ b/extlib/mimalloc/docs/search/all_0.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['_5fmi_5foption_5flast',['_mi_option_last',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca5b4357b74be0d87568036c32eb1a2e4a',1,'mimalloc-doc.h']]] +]; diff --git a/extlib/mimalloc/docs/search/all_1.html b/extlib/mimalloc/docs/search/all_1.html new file mode 100644 index 0000000..2f46793 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_1.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_1.js b/extlib/mimalloc/docs/search/all_1.js new file mode 100644 index 0000000..bbb4b54 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_1.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['aligned_20allocation',['Aligned Allocation',['../group__aligned.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/all_2.html b/extlib/mimalloc/docs/search/all_2.html new file mode 100644 index 0000000..4c33d85 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_2.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_2.js b/extlib/mimalloc/docs/search/all_2.js new file mode 100644 index 0000000..829288c --- /dev/null +++ b/extlib/mimalloc/docs/search/all_2.js @@ -0,0 +1,7 @@ +var searchData= +[ + ['block_5fsize',['block_size',['../group__analysis.html#a332a6c14d736a99699d5453a1cb04b41',1,'mi_heap_area_t']]], + ['blocks',['blocks',['../group__analysis.html#ae0085e6e1cf059a4eb7767e30e9991b8',1,'mi_heap_area_t']]], + ['building',['Building',['../build.html',1,'']]], + ['basic_20allocation',['Basic Allocation',['../group__malloc.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/all_3.html b/extlib/mimalloc/docs/search/all_3.html new file mode 100644 index 0000000..b634070 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_3.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_3.js b/extlib/mimalloc/docs/search/all_3.js new file mode 100644 index 0000000..2e08411 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_3.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['committed',['committed',['../group__analysis.html#ab47526df656d8837ec3e97f11b83f835',1,'mi_heap_area_t']]], + ['c_2b_2b_20wrappers',['C++ wrappers',['../group__cpp.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/all_4.html b/extlib/mimalloc/docs/search/all_4.html new file mode 100644 index 0000000..dd062ae --- /dev/null +++ b/extlib/mimalloc/docs/search/all_4.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_4.js b/extlib/mimalloc/docs/search/all_4.js new file mode 100644 index 0000000..059f44c --- /dev/null +++ b/extlib/mimalloc/docs/search/all_4.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['environment_20options',['Environment Options',['../environment.html',1,'']]], + ['extended_20functions',['Extended Functions',['../group__extended.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/all_5.html b/extlib/mimalloc/docs/search/all_5.html new file mode 100644 index 0000000..f0780fd --- /dev/null +++ b/extlib/mimalloc/docs/search/all_5.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_5.js b/extlib/mimalloc/docs/search/all_5.js new file mode 100644 index 0000000..e7e4093 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_5.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['heap_20introspection',['Heap Introspection',['../group__analysis.html',1,'']]], + ['heap_20allocation',['Heap Allocation',['../group__heap.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/all_6.html b/extlib/mimalloc/docs/search/all_6.html new file mode 100644 index 0000000..39b0f55 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_6.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_6.js b/extlib/mimalloc/docs/search/all_6.js new file mode 100644 index 0000000..5c2aa01 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_6.js @@ -0,0 +1,147 @@ +var searchData= +[ + ['mi_5f_5fposix_5fmemalign',['mi__posix_memalign',['../group__posix.html#gad5a69c8fea96aa2b7a7c818c2130090a',1,'mimalloc-doc.h']]], + ['mi_5faligned_5falloc',['mi_aligned_alloc',['../group__posix.html#ga1326d2e4388630b5f81ca7206318b8e5',1,'mimalloc-doc.h']]], + ['mi_5fblock_5fvisit_5ffun',['mi_block_visit_fun',['../group__analysis.html#gadfa01e2900f0e5d515ad5506b26f6d65',1,'mimalloc-doc.h']]], + ['mi_5fcalloc',['mi_calloc',['../group__malloc.html#ga97fedb4f7107c592fd7f0f0a8949a57d',1,'mimalloc-doc.h']]], + ['mi_5fcalloc_5faligned',['mi_calloc_aligned',['../group__aligned.html#ga53dddb4724042a90315b94bc268fb4c9',1,'mimalloc-doc.h']]], + ['mi_5fcalloc_5faligned_5fat',['mi_calloc_aligned_at',['../group__aligned.html#ga08647c4593f3b2eef24a919a73eba3a3',1,'mimalloc-doc.h']]], + ['mi_5fcalloc_5ftp',['mi_calloc_tp',['../group__typed.html#gae80c47c9d4cab10961fff1a8ac98fc07',1,'mimalloc-doc.h']]], + ['mi_5fcfree',['mi_cfree',['../group__posix.html#ga705dc7a64bffacfeeb0141501a5c35d7',1,'mimalloc-doc.h']]], + ['mi_5fcheck_5fowned',['mi_check_owned',['../group__analysis.html#ga628c237489c2679af84a4d0d143b3dd5',1,'mimalloc-doc.h']]], + ['mi_5fcollect',['mi_collect',['../group__extended.html#ga421430e2226d7d468529cec457396756',1,'mimalloc-doc.h']]], + ['mi_5fdeferred_5ffree_5ffun',['mi_deferred_free_fun',['../group__extended.html#ga299dae78d25ce112e384a98b7309c5be',1,'mimalloc-doc.h']]], + ['mi_5ferror_5ffun',['mi_error_fun',['../group__extended.html#ga251d369cda3f1c2a955c555486ed90e5',1,'mimalloc-doc.h']]], + ['mi_5fexpand',['mi_expand',['../group__malloc.html#gaaee66a1d483c3e28f585525fb96707e4',1,'mimalloc-doc.h']]], + ['mi_5ffree',['mi_free',['../group__malloc.html#gaf2c7b89c327d1f60f59e68b9ea644d95',1,'mimalloc-doc.h']]], + ['mi_5ffree_5faligned',['mi_free_aligned',['../group__posix.html#ga0d28d5cf61e6bfbb18c63092939fe5c9',1,'mimalloc-doc.h']]], + ['mi_5ffree_5fsize',['mi_free_size',['../group__posix.html#gae01389eedab8d67341ff52e2aad80ebb',1,'mimalloc-doc.h']]], + ['mi_5ffree_5fsize_5faligned',['mi_free_size_aligned',['../group__posix.html#ga72e9d7ffb5fe94d69bc722c8506e27bc',1,'mimalloc-doc.h']]], + ['mi_5fgood_5fsize',['mi_good_size',['../group__extended.html#gac057927cd06c854b45fe7847e921bd47',1,'mimalloc-doc.h']]], + ['mi_5fheap_5farea_5ft',['mi_heap_area_t',['../group__analysis.html#structmi__heap__area__t',1,'']]], + ['mi_5fheap_5fcalloc',['mi_heap_calloc',['../group__heap.html#gaa6702b3c48e9e53e50e81b36f5011d55',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcalloc_5faligned',['mi_heap_calloc_aligned',['../group__heap.html#ga4af03a6e2b93fae77424d93f889705c3',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcalloc_5faligned_5fat',['mi_heap_calloc_aligned_at',['../group__heap.html#ga08ca6419a5c057a4d965868998eef487',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcalloc_5ftp',['mi_heap_calloc_tp',['../group__typed.html#ga4e5d1f1707c90e5f55e023ac5f45fe74',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcheck_5fowned',['mi_heap_check_owned',['../group__analysis.html#ga0d67c1789faaa15ff366c024fcaf6377',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcollect',['mi_heap_collect',['../group__heap.html#ga7922f7495cde30b1984d0e6072419298',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcontains_5fblock',['mi_heap_contains_block',['../group__analysis.html#gaa862aa8ed8d57d84cae41fc1022d71af',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fdelete',['mi_heap_delete',['../group__heap.html#ga2ab1af8d438819b55319c7ef51d1e409',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fdestroy',['mi_heap_destroy',['../group__heap.html#ga9f9c0844edb9717f4feacd79116b8e0d',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fget_5fbacking',['mi_heap_get_backing',['../group__heap.html#ga5d03fbe062ffcf38f0f417fd968357fc',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fget_5fdefault',['mi_heap_get_default',['../group__heap.html#ga8db4cbb87314a989a9a187464d6b5e05',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmalloc',['mi_heap_malloc',['../group__heap.html#ga9cbed01e42c0647907295de92c3fa296',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmalloc_5faligned',['mi_heap_malloc_aligned',['../group__heap.html#gab5b87e1805306f70df38789fcfcf6653',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmalloc_5faligned_5fat',['mi_heap_malloc_aligned_at',['../group__heap.html#ga23acd7680fb0976dde3783254c6c874b',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmalloc_5fsmall',['mi_heap_malloc_small',['../group__heap.html#gaa1a1c7a1f4da6826b5a25b70ef878368',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmalloc_5ftp',['mi_heap_malloc_tp',['../group__typed.html#ga653bcb24ac495bc19940ecd6898f9cd7',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmallocn',['mi_heap_mallocn',['../group__heap.html#ga851da6c43fe0b71c1376cee8aef90db0',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmallocn_5ftp',['mi_heap_mallocn_tp',['../group__typed.html#ga6b75cb9c4b9c647661d0924552dc6e83',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fnew',['mi_heap_new',['../group__heap.html#ga766f672ba56f2fbfeb9d9dbb0b7f6b11',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frealloc',['mi_heap_realloc',['../group__heap.html#gaaef3395f66be48f37bdc8322509c5d81',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frealloc_5faligned',['mi_heap_realloc_aligned',['../group__heap.html#gafc603b696bd14cae6da28658f950d98c',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frealloc_5faligned_5fat',['mi_heap_realloc_aligned_at',['../group__heap.html#gaf96c788a1bf553fe2d371de9365e047c',1,'mimalloc-doc.h']]], + ['mi_5fheap_5freallocf',['mi_heap_reallocf',['../group__heap.html#ga4a21070eb4e7cce018133c8d5f4b0527',1,'mimalloc-doc.h']]], + ['mi_5fheap_5freallocn',['mi_heap_reallocn',['../group__heap.html#gac74e94ad9b0c9b57c1c4d88b8825b7a8',1,'mimalloc-doc.h']]], + ['mi_5fheap_5freallocn_5ftp',['mi_heap_reallocn_tp',['../group__typed.html#gaf213d5422ec35e7f6caad827c79bc948',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frealpath',['mi_heap_realpath',['../group__heap.html#ga00e95ba1e01acac3cfd95bb7a357a6f0',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frecalloc',['mi_heap_recalloc',['../group__zeroinit.html#ga8648c5fbb22a80f0262859099f06dfbd',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frecalloc_5faligned',['mi_heap_recalloc_aligned',['../group__zeroinit.html#ga9f3f999396c8f77ca5e80e7b40ac29e3',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frecalloc_5faligned_5fat',['mi_heap_recalloc_aligned_at',['../group__zeroinit.html#ga496452c96f1de8c500be9fddf52edaf7',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frecalloc_5ftp',['mi_heap_recalloc_tp',['../group__typed.html#ga3e50a1600958fcaf1a7f3560c9174f9e',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frezalloc',['mi_heap_rezalloc',['../group__zeroinit.html#gacfad83f14eb5d6a42a497a898e19fc76',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frezalloc_5faligned',['mi_heap_rezalloc_aligned',['../group__zeroinit.html#ga375fa8a611c51905e592d5d467c49664',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frezalloc_5faligned_5fat',['mi_heap_rezalloc_aligned_at',['../group__zeroinit.html#gac90da54fa7e5d10bdc97ce0b51dce2eb',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fset_5fdefault',['mi_heap_set_default',['../group__heap.html#gab8631ec88c8d26641b68b5d25dcd4422',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fstrdup',['mi_heap_strdup',['../group__heap.html#ga139d6b09dbf50c3c2523d0f4d1cfdeb5',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fstrndup',['mi_heap_strndup',['../group__heap.html#ga8e3dbd46650dd26573cf307a2c8f1f5a',1,'mimalloc-doc.h']]], + ['mi_5fheap_5ft',['mi_heap_t',['../group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fvisit_5fblocks',['mi_heap_visit_blocks',['../group__analysis.html#ga70c46687dc6e9dc98b232b02646f8bed',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fzalloc',['mi_heap_zalloc',['../group__heap.html#ga903104592c8ed53417a3762da6241133',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fzalloc_5faligned',['mi_heap_zalloc_aligned',['../group__heap.html#gaa450a59c6c7ae5fdbd1c2b80a8329ef0',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fzalloc_5faligned_5fat',['mi_heap_zalloc_aligned_at',['../group__heap.html#ga45fb43a62776fbebbdf1edd99b527954',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fzalloc_5ftp',['mi_heap_zalloc_tp',['../group__typed.html#gad6e87e86e994aa14416ae9b5d4c188fe',1,'mimalloc-doc.h']]], + ['mi_5fis_5fin_5fheap_5fregion',['mi_is_in_heap_region',['../group__extended.html#ga5f071b10d4df1c3658e04e7fd67a94e6',1,'mimalloc-doc.h']]], + ['mi_5fis_5fredirected',['mi_is_redirected',['../group__extended.html#gaad25050b19f30cd79397b227e0157a3f',1,'mimalloc-doc.h']]], + ['mi_5fmalloc',['mi_malloc',['../group__malloc.html#ga3406e8b168bc74c8637b11571a6da83a',1,'mimalloc-doc.h']]], + ['mi_5fmalloc_5faligned',['mi_malloc_aligned',['../group__aligned.html#ga68930196751fa2cca9e1fd0d71bade56',1,'mimalloc-doc.h']]], + ['mi_5fmalloc_5faligned_5fat',['mi_malloc_aligned_at',['../group__aligned.html#ga5850da130c936bd77db039dcfbc8295d',1,'mimalloc-doc.h']]], + ['mi_5fmalloc_5fsize',['mi_malloc_size',['../group__posix.html#ga4531c9e775bb3ae12db57c1ba8a5d7de',1,'mimalloc-doc.h']]], + ['mi_5fmalloc_5fsmall',['mi_malloc_small',['../group__extended.html#ga7136c2e55cb22c98ecf95d08d6debb99',1,'mimalloc-doc.h']]], + ['mi_5fmalloc_5ftp',['mi_malloc_tp',['../group__typed.html#ga0619a62c5fd886f1016030abe91f0557',1,'mimalloc-doc.h']]], + ['mi_5fmalloc_5fusable_5fsize',['mi_malloc_usable_size',['../group__posix.html#ga06d07cf357bbac5c73ba5d0c0c421e17',1,'mimalloc-doc.h']]], + ['mi_5fmallocn',['mi_mallocn',['../group__malloc.html#ga0b05e2bf0f73e7401ae08597ff782ac6',1,'mimalloc-doc.h']]], + ['mi_5fmallocn_5ftp',['mi_mallocn_tp',['../group__typed.html#gae5cb6e0fafc9f23169c5622e077afe8b',1,'mimalloc-doc.h']]], + ['mi_5fmemalign',['mi_memalign',['../group__posix.html#gaab7fa71ea93b96873f5d9883db57d40e',1,'mimalloc-doc.h']]], + ['mi_5fnew',['mi_new',['../group__cpp.html#gaad048a9fce3d02c5909cd05c6ec24545',1,'mimalloc-doc.h']]], + ['mi_5fnew_5faligned',['mi_new_aligned',['../group__cpp.html#gaef2c2bdb4f70857902d3c8903ac095f3',1,'mimalloc-doc.h']]], + ['mi_5fnew_5faligned_5fnothrow',['mi_new_aligned_nothrow',['../group__cpp.html#gab5e29558926d934c3f1cae8c815f942c',1,'mimalloc-doc.h']]], + ['mi_5fnew_5fn',['mi_new_n',['../group__cpp.html#gae7bc4f56cd57ed3359060ff4f38bda81',1,'mimalloc-doc.h']]], + ['mi_5fnew_5fnothrow',['mi_new_nothrow',['../group__cpp.html#gaeaded64eda71ed6b1d569d3e723abc4a',1,'mimalloc-doc.h']]], + ['mi_5fnew_5frealloc',['mi_new_realloc',['../group__cpp.html#gaab78a32f55149e9fbf432d5288e38e1e',1,'mimalloc-doc.h']]], + ['mi_5fnew_5freallocn',['mi_new_reallocn',['../group__cpp.html#ga756f4b2bc6a7ecd0a90baea8e90c7907',1,'mimalloc-doc.h']]], + ['mi_5foption_5fdisable',['mi_option_disable',['../group__options.html#gaebf6ff707a2e688ebb1a2296ca564054',1,'mimalloc-doc.h']]], + ['mi_5foption_5feager_5fcommit',['mi_option_eager_commit',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca1e8de72c93da7ff22d91e1e27b52ac2b',1,'mimalloc-doc.h']]], + ['mi_5foption_5feager_5fcommit_5fdelay',['mi_option_eager_commit_delay',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca17a190c25be381142d87e0468c4c068c',1,'mimalloc-doc.h']]], + ['mi_5foption_5feager_5fregion_5fcommit',['mi_option_eager_region_commit',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca32ce97ece29f69e82579679cf8a307ad',1,'mimalloc-doc.h']]], + ['mi_5foption_5fenable',['mi_option_enable',['../group__options.html#ga04180ae41b0d601421dd62ced40ca050',1,'mimalloc-doc.h']]], + ['mi_5foption_5fget',['mi_option_get',['../group__options.html#ga7e8af195cc81d3fa64ccf2662caa565a',1,'mimalloc-doc.h']]], + ['mi_5foption_5fis_5fenabled',['mi_option_is_enabled',['../group__options.html#ga459ad98f18b3fc9275474807fe0ca188',1,'mimalloc-doc.h']]], + ['mi_5foption_5flarge_5fos_5fpages',['mi_option_large_os_pages',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4192d491200d0055df0554d4cf65054e',1,'mimalloc-doc.h']]], + ['mi_5foption_5fos_5ftag',['mi_option_os_tag',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4b74ae2a69e445de6c2361b73c1d14bf',1,'mimalloc-doc.h']]], + ['mi_5foption_5fpage_5freset',['mi_option_page_reset',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cada854dd272c66342f18a93ee254a2968',1,'mimalloc-doc.h']]], + ['mi_5foption_5freserve_5fhuge_5fos_5fpages',['mi_option_reserve_huge_os_pages',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884caca7ed041be3b0b9d0b82432c7bf41af2',1,'mimalloc-doc.h']]], + ['mi_5foption_5freset_5fdecommits',['mi_option_reset_decommits',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cac81ee965b130fa81238913a3c239d536',1,'mimalloc-doc.h']]], + ['mi_5foption_5freset_5fdelay',['mi_option_reset_delay',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca154fe170131d5212cff57e22b99523c5',1,'mimalloc-doc.h']]], + ['mi_5foption_5fsegment_5fcache',['mi_option_segment_cache',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca2ecbe7ef32f5c84de3739aa4f0b805a1',1,'mimalloc-doc.h']]], + ['mi_5foption_5fsegment_5freset',['mi_option_segment_reset',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafb121d30d87591850d5410ccc3a95c6d',1,'mimalloc-doc.h']]], + ['mi_5foption_5fset',['mi_option_set',['../group__options.html#gaf84921c32375e25754dc2ee6a911fa60',1,'mimalloc-doc.h']]], + ['mi_5foption_5fset_5fdefault',['mi_option_set_default',['../group__options.html#ga7ef623e440e6e5545cb08c94e71e4b90',1,'mimalloc-doc.h']]], + ['mi_5foption_5fset_5fenabled',['mi_option_set_enabled',['../group__options.html#ga9a13d05fcb77489cb06d4d017ebd8bed',1,'mimalloc-doc.h']]], + ['mi_5foption_5fset_5fenabled_5fdefault',['mi_option_set_enabled_default',['../group__options.html#ga65518b69ec5d32336b50e07f74b3f629',1,'mimalloc-doc.h']]], + ['mi_5foption_5fshow_5ferrors',['mi_option_show_errors',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafbf4822e5c00732c5984b32a032837f0',1,'mimalloc-doc.h']]], + ['mi_5foption_5fshow_5fstats',['mi_option_show_stats',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0957ef73b2550764b4840edf48422fda',1,'mimalloc-doc.h']]], + ['mi_5foption_5ft',['mi_option_t',['../group__options.html#gafebf7ed116adb38ae5218bc3ce06884c',1,'mimalloc-doc.h']]], + ['mi_5foption_5fuse_5fnuma_5fnodes',['mi_option_use_numa_nodes',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0ac33a18f6b659fcfaf44efb0bab1b74',1,'mimalloc-doc.h']]], + ['mi_5foption_5fverbose',['mi_option_verbose',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca7c8b7bf5281c581bad64f5daa6442777',1,'mimalloc-doc.h']]], + ['mi_5foutput_5ffun',['mi_output_fun',['../group__extended.html#gad823d23444a4b77a40f66bf075a98a0c',1,'mimalloc-doc.h']]], + ['mi_5fposix_5fmemalign',['mi_posix_memalign',['../group__posix.html#gacff84f226ba9feb2031b8992e5579447',1,'mimalloc-doc.h']]], + ['mi_5fpvalloc',['mi_pvalloc',['../group__posix.html#gaeb325c39b887d3b90d85d1eb1712fb1e',1,'mimalloc-doc.h']]], + ['mi_5frealloc',['mi_realloc',['../group__malloc.html#gaf11eb497da57bdfb2de65eb191c69db6',1,'mimalloc-doc.h']]], + ['mi_5frealloc_5faligned',['mi_realloc_aligned',['../group__aligned.html#ga4028d1cf4aa4c87c880747044a8322ae',1,'mimalloc-doc.h']]], + ['mi_5frealloc_5faligned_5fat',['mi_realloc_aligned_at',['../group__aligned.html#gaf66a9ae6c6f08bd6be6fb6ea771faffb',1,'mimalloc-doc.h']]], + ['mi_5freallocarray',['mi_reallocarray',['../group__posix.html#ga48fad8648a2f1dab9c87ea9448a52088',1,'mimalloc-doc.h']]], + ['mi_5freallocf',['mi_reallocf',['../group__malloc.html#gafe68ac7c5e24a65cd55c9d6b152211a0',1,'mimalloc-doc.h']]], + ['mi_5freallocn',['mi_reallocn',['../group__malloc.html#ga61d57b4144ba24fba5c1e9b956d13853',1,'mimalloc-doc.h']]], + ['mi_5freallocn_5ftp',['mi_reallocn_tp',['../group__typed.html#ga1158b49a55dfa81f58a4426a7578f523',1,'mimalloc-doc.h']]], + ['mi_5frealpath',['mi_realpath',['../group__malloc.html#ga08cec32dd5bbe7da91c78d19f1b5bebe',1,'mimalloc-doc.h']]], + ['mi_5frecalloc',['mi_recalloc',['../group__malloc.html#ga23a0fbb452b5dce8e31fab1a1958cacc',1,'mimalloc-doc.h']]], + ['mi_5frecalloc_5faligned',['mi_recalloc_aligned',['../group__zeroinit.html#ga3e7e5c291acf1c7fd7ffd9914a9f945f',1,'mimalloc-doc.h']]], + ['mi_5frecalloc_5faligned_5fat',['mi_recalloc_aligned_at',['../group__zeroinit.html#ga4ff5e92ad73585418a072c9d059e5cf9',1,'mimalloc-doc.h']]], + ['mi_5fregister_5fdeferred_5ffree',['mi_register_deferred_free',['../group__extended.html#ga3460a6ca91af97be4058f523d3cb8ece',1,'mimalloc-doc.h']]], + ['mi_5fregister_5ferror',['mi_register_error',['../group__extended.html#gaa1d55e0e894be240827e5d87ec3a1f45',1,'mimalloc-doc.h']]], + ['mi_5fregister_5foutput',['mi_register_output',['../group__extended.html#gae5b17ff027cd2150b43a33040250cf3f',1,'mimalloc-doc.h']]], + ['mi_5freserve_5fhuge_5fos_5fpages_5fat',['mi_reserve_huge_os_pages_at',['../group__extended.html#ga7795a13d20087447281858d2c771cca1',1,'mimalloc-doc.h']]], + ['mi_5freserve_5fhuge_5fos_5fpages_5finterleave',['mi_reserve_huge_os_pages_interleave',['../group__extended.html#ga3132f521fb756fc0e8ec0b74fb58df50',1,'mimalloc-doc.h']]], + ['mi_5frezalloc',['mi_rezalloc',['../group__zeroinit.html#ga8c292e142110229a2980b37ab036dbc6',1,'mimalloc-doc.h']]], + ['mi_5frezalloc_5faligned',['mi_rezalloc_aligned',['../group__zeroinit.html#gacd71a7bce96aab38ae6de17af2eb2cf0',1,'mimalloc-doc.h']]], + ['mi_5frezalloc_5faligned_5fat',['mi_rezalloc_aligned_at',['../group__zeroinit.html#gae8b358c417e61d5307da002702b0a8e1',1,'mimalloc-doc.h']]], + ['mi_5fsmall_5fsize_5fmax',['MI_SMALL_SIZE_MAX',['../group__extended.html#ga1ea64283508718d9d645c38efc2f4305',1,'mimalloc-doc.h']]], + ['mi_5fstats_5fmerge',['mi_stats_merge',['../group__extended.html#ga854b1de8cb067c7316286c28b2fcd3d1',1,'mimalloc-doc.h']]], + ['mi_5fstats_5fprint',['mi_stats_print',['../group__extended.html#ga2d126e5c62d3badc35445e5d84166df2',1,'mimalloc-doc.h']]], + ['mi_5fstats_5fprint_5fout',['mi_stats_print_out',['../group__extended.html#ga537f13b299ddf801e49a5a94fde02c79',1,'mimalloc-doc.h']]], + ['mi_5fstats_5freset',['mi_stats_reset',['../group__extended.html#ga3bb8468b8cfcc6e2a61d98aee85c5f99',1,'mimalloc-doc.h']]], + ['mi_5fstl_5fallocator',['mi_stl_allocator',['../group__cpp.html#structmi__stl__allocator',1,'']]], + ['mi_5fstrdup',['mi_strdup',['../group__malloc.html#gac7cffe13f1f458ed16789488bf92b9b2',1,'mimalloc-doc.h']]], + ['mi_5fstrndup',['mi_strndup',['../group__malloc.html#gaaabf971c2571891433477e2d21a35266',1,'mimalloc-doc.h']]], + ['mi_5fthread_5fdone',['mi_thread_done',['../group__extended.html#ga0ae4581e85453456a0d658b2b98bf7bf',1,'mimalloc-doc.h']]], + ['mi_5fthread_5finit',['mi_thread_init',['../group__extended.html#gaf8e73efc2cbca9ebfdfb166983a04c17',1,'mimalloc-doc.h']]], + ['mi_5fthread_5fstats_5fprint_5fout',['mi_thread_stats_print_out',['../group__extended.html#gab1dac8476c46cb9eecab767eb40c1525',1,'mimalloc-doc.h']]], + ['mi_5fusable_5fsize',['mi_usable_size',['../group__extended.html#ga089c859d9eddc5f9b4bd946cd53cebee',1,'mimalloc-doc.h']]], + ['mi_5fvalloc',['mi_valloc',['../group__posix.html#ga73baaf5951f5165ba0763d0c06b6a93b',1,'mimalloc-doc.h']]], + ['mi_5fzalloc',['mi_zalloc',['../group__malloc.html#gafdd9d8bb2986e668ba9884f28af38000',1,'mimalloc-doc.h']]], + ['mi_5fzalloc_5faligned',['mi_zalloc_aligned',['../group__aligned.html#ga0cadbcf5b89a7b6fb171bc8df8734819',1,'mimalloc-doc.h']]], + ['mi_5fzalloc_5faligned_5fat',['mi_zalloc_aligned_at',['../group__aligned.html#ga5f8c2353766db522565e642fafd8a3f8',1,'mimalloc-doc.h']]], + ['mi_5fzalloc_5fsmall',['mi_zalloc_small',['../group__extended.html#ga220f29f40a44404b0061c15bc1c31152',1,'mimalloc-doc.h']]], + ['mi_5fzalloc_5ftp',['mi_zalloc_tp',['../group__typed.html#gac77a61bdaf680a803785fe307820b48c',1,'mimalloc-doc.h']]] +]; diff --git a/extlib/mimalloc/docs/search/all_7.html b/extlib/mimalloc/docs/search/all_7.html new file mode 100644 index 0000000..9cd0196 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_7.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_7.js b/extlib/mimalloc/docs/search/all_7.js new file mode 100644 index 0000000..df03c8d --- /dev/null +++ b/extlib/mimalloc/docs/search/all_7.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['overriding_20malloc',['Overriding Malloc',['../overrides.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/all_8.html b/extlib/mimalloc/docs/search/all_8.html new file mode 100644 index 0000000..1e8fb9c --- /dev/null +++ b/extlib/mimalloc/docs/search/all_8.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_8.js b/extlib/mimalloc/docs/search/all_8.js new file mode 100644 index 0000000..0651bcc --- /dev/null +++ b/extlib/mimalloc/docs/search/all_8.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['performance',['Performance',['../bench.html',1,'']]], + ['posix',['Posix',['../group__posix.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/all_9.html b/extlib/mimalloc/docs/search/all_9.html new file mode 100644 index 0000000..27df366 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_9.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_9.js b/extlib/mimalloc/docs/search/all_9.js new file mode 100644 index 0000000..cd78624 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_9.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['runtime_20options',['Runtime Options',['../group__options.html',1,'']]], + ['reserved',['reserved',['../group__analysis.html#ae848a3e6840414891035423948ca0383',1,'mi_heap_area_t']]] +]; diff --git a/extlib/mimalloc/docs/search/all_a.html b/extlib/mimalloc/docs/search/all_a.html new file mode 100644 index 0000000..63f9254 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_a.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_a.js b/extlib/mimalloc/docs/search/all_a.js new file mode 100644 index 0000000..647887f --- /dev/null +++ b/extlib/mimalloc/docs/search/all_a.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['typed_20macros',['Typed Macros',['../group__typed.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/all_b.html b/extlib/mimalloc/docs/search/all_b.html new file mode 100644 index 0000000..44ae3e4 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_b.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_b.js b/extlib/mimalloc/docs/search/all_b.js new file mode 100644 index 0000000..2bc3fb6 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_b.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['used',['used',['../group__analysis.html#ab820302c5cd0df133eb8e51650a008b4',1,'mi_heap_area_t']]], + ['using_20the_20library',['Using the library',['../using.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/all_c.html b/extlib/mimalloc/docs/search/all_c.html new file mode 100644 index 0000000..3de1586 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_c.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_c.js b/extlib/mimalloc/docs/search/all_c.js new file mode 100644 index 0000000..2b9b4ce --- /dev/null +++ b/extlib/mimalloc/docs/search/all_c.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['zero_20initialized_20re_2dallocation',['Zero initialized re-allocation',['../group__zeroinit.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/all_d.html b/extlib/mimalloc/docs/search/all_d.html new file mode 100644 index 0000000..a2d5bd7 --- /dev/null +++ b/extlib/mimalloc/docs/search/all_d.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/all_d.js b/extlib/mimalloc/docs/search/all_d.js new file mode 100644 index 0000000..2b9b4ce --- /dev/null +++ b/extlib/mimalloc/docs/search/all_d.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['zero_20initialized_20re_2dallocation',['Zero initialized re-allocation',['../group__zeroinit.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/classes_0.html b/extlib/mimalloc/docs/search/classes_0.html new file mode 100644 index 0000000..b3c6ec6 --- /dev/null +++ b/extlib/mimalloc/docs/search/classes_0.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/classes_0.js b/extlib/mimalloc/docs/search/classes_0.js new file mode 100644 index 0000000..0010dd9 --- /dev/null +++ b/extlib/mimalloc/docs/search/classes_0.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['mi_5fheap_5farea_5ft',['mi_heap_area_t',['../group__analysis.html#structmi__heap__area__t',1,'']]], + ['mi_5fstl_5fallocator',['mi_stl_allocator',['../group__cpp.html#structmi__stl__allocator',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/close.png b/extlib/mimalloc/docs/search/close.png new file mode 100644 index 0000000000000000000000000000000000000000..9342d3dfeea7b7c4ee610987e717804b5a42ceb9 GIT binary patch literal 273 zcmV+s0q*{ZP)4(RlMby96)VwnbG{ zbe&}^BDn7x>$<{ck4zAK-=nT;=hHG)kmplIF${xqm8db3oX6wT3bvp`TE@m0cg;b) zBuSL}5?N7O(iZLdAlz@)b)Rd~DnSsSX&P5qC`XwuFwcAYLC+d2>+1(8on;wpt8QIC X2MT$R4iQDd00000NkvXXu0mjfia~GN literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/search/enums_0.html b/extlib/mimalloc/docs/search/enums_0.html new file mode 100644 index 0000000..7040a9c --- /dev/null +++ b/extlib/mimalloc/docs/search/enums_0.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/enums_0.js b/extlib/mimalloc/docs/search/enums_0.js new file mode 100644 index 0000000..f0c1ba5 --- /dev/null +++ b/extlib/mimalloc/docs/search/enums_0.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['mi_5foption_5ft',['mi_option_t',['../group__options.html#gafebf7ed116adb38ae5218bc3ce06884c',1,'mimalloc-doc.h']]] +]; diff --git a/extlib/mimalloc/docs/search/enumvalues_0.html b/extlib/mimalloc/docs/search/enumvalues_0.html new file mode 100644 index 0000000..78895c7 --- /dev/null +++ b/extlib/mimalloc/docs/search/enumvalues_0.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/enumvalues_0.js b/extlib/mimalloc/docs/search/enumvalues_0.js new file mode 100644 index 0000000..7054b6d --- /dev/null +++ b/extlib/mimalloc/docs/search/enumvalues_0.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['_5fmi_5foption_5flast',['_mi_option_last',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca5b4357b74be0d87568036c32eb1a2e4a',1,'mimalloc-doc.h']]] +]; diff --git a/extlib/mimalloc/docs/search/enumvalues_1.html b/extlib/mimalloc/docs/search/enumvalues_1.html new file mode 100644 index 0000000..9b02a4b --- /dev/null +++ b/extlib/mimalloc/docs/search/enumvalues_1.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/enumvalues_1.js b/extlib/mimalloc/docs/search/enumvalues_1.js new file mode 100644 index 0000000..3b71270 --- /dev/null +++ b/extlib/mimalloc/docs/search/enumvalues_1.js @@ -0,0 +1,18 @@ +var searchData= +[ + ['mi_5foption_5feager_5fcommit',['mi_option_eager_commit',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca1e8de72c93da7ff22d91e1e27b52ac2b',1,'mimalloc-doc.h']]], + ['mi_5foption_5feager_5fcommit_5fdelay',['mi_option_eager_commit_delay',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca17a190c25be381142d87e0468c4c068c',1,'mimalloc-doc.h']]], + ['mi_5foption_5feager_5fregion_5fcommit',['mi_option_eager_region_commit',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca32ce97ece29f69e82579679cf8a307ad',1,'mimalloc-doc.h']]], + ['mi_5foption_5flarge_5fos_5fpages',['mi_option_large_os_pages',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4192d491200d0055df0554d4cf65054e',1,'mimalloc-doc.h']]], + ['mi_5foption_5fos_5ftag',['mi_option_os_tag',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca4b74ae2a69e445de6c2361b73c1d14bf',1,'mimalloc-doc.h']]], + ['mi_5foption_5fpage_5freset',['mi_option_page_reset',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cada854dd272c66342f18a93ee254a2968',1,'mimalloc-doc.h']]], + ['mi_5foption_5freserve_5fhuge_5fos_5fpages',['mi_option_reserve_huge_os_pages',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884caca7ed041be3b0b9d0b82432c7bf41af2',1,'mimalloc-doc.h']]], + ['mi_5foption_5freset_5fdecommits',['mi_option_reset_decommits',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cac81ee965b130fa81238913a3c239d536',1,'mimalloc-doc.h']]], + ['mi_5foption_5freset_5fdelay',['mi_option_reset_delay',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca154fe170131d5212cff57e22b99523c5',1,'mimalloc-doc.h']]], + ['mi_5foption_5fsegment_5fcache',['mi_option_segment_cache',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca2ecbe7ef32f5c84de3739aa4f0b805a1',1,'mimalloc-doc.h']]], + ['mi_5foption_5fsegment_5freset',['mi_option_segment_reset',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafb121d30d87591850d5410ccc3a95c6d',1,'mimalloc-doc.h']]], + ['mi_5foption_5fshow_5ferrors',['mi_option_show_errors',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884cafbf4822e5c00732c5984b32a032837f0',1,'mimalloc-doc.h']]], + ['mi_5foption_5fshow_5fstats',['mi_option_show_stats',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0957ef73b2550764b4840edf48422fda',1,'mimalloc-doc.h']]], + ['mi_5foption_5fuse_5fnuma_5fnodes',['mi_option_use_numa_nodes',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca0ac33a18f6b659fcfaf44efb0bab1b74',1,'mimalloc-doc.h']]], + ['mi_5foption_5fverbose',['mi_option_verbose',['../group__options.html#ggafebf7ed116adb38ae5218bc3ce06884ca7c8b7bf5281c581bad64f5daa6442777',1,'mimalloc-doc.h']]] +]; diff --git a/extlib/mimalloc/docs/search/functions_0.html b/extlib/mimalloc/docs/search/functions_0.html new file mode 100644 index 0000000..bc73761 --- /dev/null +++ b/extlib/mimalloc/docs/search/functions_0.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/functions_0.js b/extlib/mimalloc/docs/search/functions_0.js new file mode 100644 index 0000000..f2b65c2 --- /dev/null +++ b/extlib/mimalloc/docs/search/functions_0.js @@ -0,0 +1,112 @@ +var searchData= +[ + ['mi_5f_5fposix_5fmemalign',['mi__posix_memalign',['../group__posix.html#gad5a69c8fea96aa2b7a7c818c2130090a',1,'mimalloc-doc.h']]], + ['mi_5faligned_5falloc',['mi_aligned_alloc',['../group__posix.html#ga1326d2e4388630b5f81ca7206318b8e5',1,'mimalloc-doc.h']]], + ['mi_5fcalloc',['mi_calloc',['../group__malloc.html#ga97fedb4f7107c592fd7f0f0a8949a57d',1,'mimalloc-doc.h']]], + ['mi_5fcalloc_5faligned',['mi_calloc_aligned',['../group__aligned.html#ga53dddb4724042a90315b94bc268fb4c9',1,'mimalloc-doc.h']]], + ['mi_5fcalloc_5faligned_5fat',['mi_calloc_aligned_at',['../group__aligned.html#ga08647c4593f3b2eef24a919a73eba3a3',1,'mimalloc-doc.h']]], + ['mi_5fcfree',['mi_cfree',['../group__posix.html#ga705dc7a64bffacfeeb0141501a5c35d7',1,'mimalloc-doc.h']]], + ['mi_5fcheck_5fowned',['mi_check_owned',['../group__analysis.html#ga628c237489c2679af84a4d0d143b3dd5',1,'mimalloc-doc.h']]], + ['mi_5fcollect',['mi_collect',['../group__extended.html#ga421430e2226d7d468529cec457396756',1,'mimalloc-doc.h']]], + ['mi_5fexpand',['mi_expand',['../group__malloc.html#gaaee66a1d483c3e28f585525fb96707e4',1,'mimalloc-doc.h']]], + ['mi_5ffree',['mi_free',['../group__malloc.html#gaf2c7b89c327d1f60f59e68b9ea644d95',1,'mimalloc-doc.h']]], + ['mi_5ffree_5faligned',['mi_free_aligned',['../group__posix.html#ga0d28d5cf61e6bfbb18c63092939fe5c9',1,'mimalloc-doc.h']]], + ['mi_5ffree_5fsize',['mi_free_size',['../group__posix.html#gae01389eedab8d67341ff52e2aad80ebb',1,'mimalloc-doc.h']]], + ['mi_5ffree_5fsize_5faligned',['mi_free_size_aligned',['../group__posix.html#ga72e9d7ffb5fe94d69bc722c8506e27bc',1,'mimalloc-doc.h']]], + ['mi_5fgood_5fsize',['mi_good_size',['../group__extended.html#gac057927cd06c854b45fe7847e921bd47',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcalloc',['mi_heap_calloc',['../group__heap.html#gaa6702b3c48e9e53e50e81b36f5011d55',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcalloc_5faligned',['mi_heap_calloc_aligned',['../group__heap.html#ga4af03a6e2b93fae77424d93f889705c3',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcalloc_5faligned_5fat',['mi_heap_calloc_aligned_at',['../group__heap.html#ga08ca6419a5c057a4d965868998eef487',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcheck_5fowned',['mi_heap_check_owned',['../group__analysis.html#ga0d67c1789faaa15ff366c024fcaf6377',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcollect',['mi_heap_collect',['../group__heap.html#ga7922f7495cde30b1984d0e6072419298',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fcontains_5fblock',['mi_heap_contains_block',['../group__analysis.html#gaa862aa8ed8d57d84cae41fc1022d71af',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fdelete',['mi_heap_delete',['../group__heap.html#ga2ab1af8d438819b55319c7ef51d1e409',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fdestroy',['mi_heap_destroy',['../group__heap.html#ga9f9c0844edb9717f4feacd79116b8e0d',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fget_5fbacking',['mi_heap_get_backing',['../group__heap.html#ga5d03fbe062ffcf38f0f417fd968357fc',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fget_5fdefault',['mi_heap_get_default',['../group__heap.html#ga8db4cbb87314a989a9a187464d6b5e05',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmalloc',['mi_heap_malloc',['../group__heap.html#ga9cbed01e42c0647907295de92c3fa296',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmalloc_5faligned',['mi_heap_malloc_aligned',['../group__heap.html#gab5b87e1805306f70df38789fcfcf6653',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmalloc_5faligned_5fat',['mi_heap_malloc_aligned_at',['../group__heap.html#ga23acd7680fb0976dde3783254c6c874b',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmalloc_5fsmall',['mi_heap_malloc_small',['../group__heap.html#gaa1a1c7a1f4da6826b5a25b70ef878368',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fmallocn',['mi_heap_mallocn',['../group__heap.html#ga851da6c43fe0b71c1376cee8aef90db0',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fnew',['mi_heap_new',['../group__heap.html#ga766f672ba56f2fbfeb9d9dbb0b7f6b11',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frealloc',['mi_heap_realloc',['../group__heap.html#gaaef3395f66be48f37bdc8322509c5d81',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frealloc_5faligned',['mi_heap_realloc_aligned',['../group__heap.html#gafc603b696bd14cae6da28658f950d98c',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frealloc_5faligned_5fat',['mi_heap_realloc_aligned_at',['../group__heap.html#gaf96c788a1bf553fe2d371de9365e047c',1,'mimalloc-doc.h']]], + ['mi_5fheap_5freallocf',['mi_heap_reallocf',['../group__heap.html#ga4a21070eb4e7cce018133c8d5f4b0527',1,'mimalloc-doc.h']]], + ['mi_5fheap_5freallocn',['mi_heap_reallocn',['../group__heap.html#gac74e94ad9b0c9b57c1c4d88b8825b7a8',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frealpath',['mi_heap_realpath',['../group__heap.html#ga00e95ba1e01acac3cfd95bb7a357a6f0',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frecalloc',['mi_heap_recalloc',['../group__zeroinit.html#ga8648c5fbb22a80f0262859099f06dfbd',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frecalloc_5faligned',['mi_heap_recalloc_aligned',['../group__zeroinit.html#ga9f3f999396c8f77ca5e80e7b40ac29e3',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frecalloc_5faligned_5fat',['mi_heap_recalloc_aligned_at',['../group__zeroinit.html#ga496452c96f1de8c500be9fddf52edaf7',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frezalloc',['mi_heap_rezalloc',['../group__zeroinit.html#gacfad83f14eb5d6a42a497a898e19fc76',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frezalloc_5faligned',['mi_heap_rezalloc_aligned',['../group__zeroinit.html#ga375fa8a611c51905e592d5d467c49664',1,'mimalloc-doc.h']]], + ['mi_5fheap_5frezalloc_5faligned_5fat',['mi_heap_rezalloc_aligned_at',['../group__zeroinit.html#gac90da54fa7e5d10bdc97ce0b51dce2eb',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fset_5fdefault',['mi_heap_set_default',['../group__heap.html#gab8631ec88c8d26641b68b5d25dcd4422',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fstrdup',['mi_heap_strdup',['../group__heap.html#ga139d6b09dbf50c3c2523d0f4d1cfdeb5',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fstrndup',['mi_heap_strndup',['../group__heap.html#ga8e3dbd46650dd26573cf307a2c8f1f5a',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fvisit_5fblocks',['mi_heap_visit_blocks',['../group__analysis.html#ga70c46687dc6e9dc98b232b02646f8bed',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fzalloc',['mi_heap_zalloc',['../group__heap.html#ga903104592c8ed53417a3762da6241133',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fzalloc_5faligned',['mi_heap_zalloc_aligned',['../group__heap.html#gaa450a59c6c7ae5fdbd1c2b80a8329ef0',1,'mimalloc-doc.h']]], + ['mi_5fheap_5fzalloc_5faligned_5fat',['mi_heap_zalloc_aligned_at',['../group__heap.html#ga45fb43a62776fbebbdf1edd99b527954',1,'mimalloc-doc.h']]], + ['mi_5fis_5fin_5fheap_5fregion',['mi_is_in_heap_region',['../group__extended.html#ga5f071b10d4df1c3658e04e7fd67a94e6',1,'mimalloc-doc.h']]], + ['mi_5fis_5fredirected',['mi_is_redirected',['../group__extended.html#gaad25050b19f30cd79397b227e0157a3f',1,'mimalloc-doc.h']]], + ['mi_5fmalloc',['mi_malloc',['../group__malloc.html#ga3406e8b168bc74c8637b11571a6da83a',1,'mimalloc-doc.h']]], + ['mi_5fmalloc_5faligned',['mi_malloc_aligned',['../group__aligned.html#ga68930196751fa2cca9e1fd0d71bade56',1,'mimalloc-doc.h']]], + ['mi_5fmalloc_5faligned_5fat',['mi_malloc_aligned_at',['../group__aligned.html#ga5850da130c936bd77db039dcfbc8295d',1,'mimalloc-doc.h']]], + ['mi_5fmalloc_5fsize',['mi_malloc_size',['../group__posix.html#ga4531c9e775bb3ae12db57c1ba8a5d7de',1,'mimalloc-doc.h']]], + ['mi_5fmalloc_5fsmall',['mi_malloc_small',['../group__extended.html#ga7136c2e55cb22c98ecf95d08d6debb99',1,'mimalloc-doc.h']]], + ['mi_5fmalloc_5fusable_5fsize',['mi_malloc_usable_size',['../group__posix.html#ga06d07cf357bbac5c73ba5d0c0c421e17',1,'mimalloc-doc.h']]], + ['mi_5fmallocn',['mi_mallocn',['../group__malloc.html#ga0b05e2bf0f73e7401ae08597ff782ac6',1,'mimalloc-doc.h']]], + ['mi_5fmemalign',['mi_memalign',['../group__posix.html#gaab7fa71ea93b96873f5d9883db57d40e',1,'mimalloc-doc.h']]], + ['mi_5fnew',['mi_new',['../group__cpp.html#gaad048a9fce3d02c5909cd05c6ec24545',1,'mimalloc-doc.h']]], + ['mi_5fnew_5faligned',['mi_new_aligned',['../group__cpp.html#gaef2c2bdb4f70857902d3c8903ac095f3',1,'mimalloc-doc.h']]], + ['mi_5fnew_5faligned_5fnothrow',['mi_new_aligned_nothrow',['../group__cpp.html#gab5e29558926d934c3f1cae8c815f942c',1,'mimalloc-doc.h']]], + ['mi_5fnew_5fn',['mi_new_n',['../group__cpp.html#gae7bc4f56cd57ed3359060ff4f38bda81',1,'mimalloc-doc.h']]], + ['mi_5fnew_5fnothrow',['mi_new_nothrow',['../group__cpp.html#gaeaded64eda71ed6b1d569d3e723abc4a',1,'mimalloc-doc.h']]], + ['mi_5fnew_5frealloc',['mi_new_realloc',['../group__cpp.html#gaab78a32f55149e9fbf432d5288e38e1e',1,'mimalloc-doc.h']]], + ['mi_5fnew_5freallocn',['mi_new_reallocn',['../group__cpp.html#ga756f4b2bc6a7ecd0a90baea8e90c7907',1,'mimalloc-doc.h']]], + ['mi_5foption_5fdisable',['mi_option_disable',['../group__options.html#gaebf6ff707a2e688ebb1a2296ca564054',1,'mimalloc-doc.h']]], + ['mi_5foption_5fenable',['mi_option_enable',['../group__options.html#ga04180ae41b0d601421dd62ced40ca050',1,'mimalloc-doc.h']]], + ['mi_5foption_5fget',['mi_option_get',['../group__options.html#ga7e8af195cc81d3fa64ccf2662caa565a',1,'mimalloc-doc.h']]], + ['mi_5foption_5fis_5fenabled',['mi_option_is_enabled',['../group__options.html#ga459ad98f18b3fc9275474807fe0ca188',1,'mimalloc-doc.h']]], + ['mi_5foption_5fset',['mi_option_set',['../group__options.html#gaf84921c32375e25754dc2ee6a911fa60',1,'mimalloc-doc.h']]], + ['mi_5foption_5fset_5fdefault',['mi_option_set_default',['../group__options.html#ga7ef623e440e6e5545cb08c94e71e4b90',1,'mimalloc-doc.h']]], + ['mi_5foption_5fset_5fenabled',['mi_option_set_enabled',['../group__options.html#ga9a13d05fcb77489cb06d4d017ebd8bed',1,'mimalloc-doc.h']]], + ['mi_5foption_5fset_5fenabled_5fdefault',['mi_option_set_enabled_default',['../group__options.html#ga65518b69ec5d32336b50e07f74b3f629',1,'mimalloc-doc.h']]], + ['mi_5fposix_5fmemalign',['mi_posix_memalign',['../group__posix.html#gacff84f226ba9feb2031b8992e5579447',1,'mimalloc-doc.h']]], + ['mi_5fpvalloc',['mi_pvalloc',['../group__posix.html#gaeb325c39b887d3b90d85d1eb1712fb1e',1,'mimalloc-doc.h']]], + ['mi_5frealloc',['mi_realloc',['../group__malloc.html#gaf11eb497da57bdfb2de65eb191c69db6',1,'mimalloc-doc.h']]], + ['mi_5frealloc_5faligned',['mi_realloc_aligned',['../group__aligned.html#ga4028d1cf4aa4c87c880747044a8322ae',1,'mimalloc-doc.h']]], + ['mi_5frealloc_5faligned_5fat',['mi_realloc_aligned_at',['../group__aligned.html#gaf66a9ae6c6f08bd6be6fb6ea771faffb',1,'mimalloc-doc.h']]], + ['mi_5freallocarray',['mi_reallocarray',['../group__posix.html#ga48fad8648a2f1dab9c87ea9448a52088',1,'mimalloc-doc.h']]], + ['mi_5freallocf',['mi_reallocf',['../group__malloc.html#gafe68ac7c5e24a65cd55c9d6b152211a0',1,'mimalloc-doc.h']]], + ['mi_5freallocn',['mi_reallocn',['../group__malloc.html#ga61d57b4144ba24fba5c1e9b956d13853',1,'mimalloc-doc.h']]], + ['mi_5frealpath',['mi_realpath',['../group__malloc.html#ga08cec32dd5bbe7da91c78d19f1b5bebe',1,'mimalloc-doc.h']]], + ['mi_5frecalloc',['mi_recalloc',['../group__malloc.html#ga23a0fbb452b5dce8e31fab1a1958cacc',1,'mimalloc-doc.h']]], + ['mi_5frecalloc_5faligned',['mi_recalloc_aligned',['../group__zeroinit.html#ga3e7e5c291acf1c7fd7ffd9914a9f945f',1,'mimalloc-doc.h']]], + ['mi_5frecalloc_5faligned_5fat',['mi_recalloc_aligned_at',['../group__zeroinit.html#ga4ff5e92ad73585418a072c9d059e5cf9',1,'mimalloc-doc.h']]], + ['mi_5fregister_5fdeferred_5ffree',['mi_register_deferred_free',['../group__extended.html#ga3460a6ca91af97be4058f523d3cb8ece',1,'mimalloc-doc.h']]], + ['mi_5fregister_5ferror',['mi_register_error',['../group__extended.html#gaa1d55e0e894be240827e5d87ec3a1f45',1,'mimalloc-doc.h']]], + ['mi_5fregister_5foutput',['mi_register_output',['../group__extended.html#gae5b17ff027cd2150b43a33040250cf3f',1,'mimalloc-doc.h']]], + ['mi_5freserve_5fhuge_5fos_5fpages_5fat',['mi_reserve_huge_os_pages_at',['../group__extended.html#ga7795a13d20087447281858d2c771cca1',1,'mimalloc-doc.h']]], + ['mi_5freserve_5fhuge_5fos_5fpages_5finterleave',['mi_reserve_huge_os_pages_interleave',['../group__extended.html#ga3132f521fb756fc0e8ec0b74fb58df50',1,'mimalloc-doc.h']]], + ['mi_5frezalloc',['mi_rezalloc',['../group__zeroinit.html#ga8c292e142110229a2980b37ab036dbc6',1,'mimalloc-doc.h']]], + ['mi_5frezalloc_5faligned',['mi_rezalloc_aligned',['../group__zeroinit.html#gacd71a7bce96aab38ae6de17af2eb2cf0',1,'mimalloc-doc.h']]], + ['mi_5frezalloc_5faligned_5fat',['mi_rezalloc_aligned_at',['../group__zeroinit.html#gae8b358c417e61d5307da002702b0a8e1',1,'mimalloc-doc.h']]], + ['mi_5fstats_5fmerge',['mi_stats_merge',['../group__extended.html#ga854b1de8cb067c7316286c28b2fcd3d1',1,'mimalloc-doc.h']]], + ['mi_5fstats_5fprint',['mi_stats_print',['../group__extended.html#ga2d126e5c62d3badc35445e5d84166df2',1,'mimalloc-doc.h']]], + ['mi_5fstats_5fprint_5fout',['mi_stats_print_out',['../group__extended.html#ga537f13b299ddf801e49a5a94fde02c79',1,'mimalloc-doc.h']]], + ['mi_5fstats_5freset',['mi_stats_reset',['../group__extended.html#ga3bb8468b8cfcc6e2a61d98aee85c5f99',1,'mimalloc-doc.h']]], + ['mi_5fstrdup',['mi_strdup',['../group__malloc.html#gac7cffe13f1f458ed16789488bf92b9b2',1,'mimalloc-doc.h']]], + ['mi_5fstrndup',['mi_strndup',['../group__malloc.html#gaaabf971c2571891433477e2d21a35266',1,'mimalloc-doc.h']]], + ['mi_5fthread_5fdone',['mi_thread_done',['../group__extended.html#ga0ae4581e85453456a0d658b2b98bf7bf',1,'mimalloc-doc.h']]], + ['mi_5fthread_5finit',['mi_thread_init',['../group__extended.html#gaf8e73efc2cbca9ebfdfb166983a04c17',1,'mimalloc-doc.h']]], + ['mi_5fthread_5fstats_5fprint_5fout',['mi_thread_stats_print_out',['../group__extended.html#gab1dac8476c46cb9eecab767eb40c1525',1,'mimalloc-doc.h']]], + ['mi_5fusable_5fsize',['mi_usable_size',['../group__extended.html#ga089c859d9eddc5f9b4bd946cd53cebee',1,'mimalloc-doc.h']]], + ['mi_5fvalloc',['mi_valloc',['../group__posix.html#ga73baaf5951f5165ba0763d0c06b6a93b',1,'mimalloc-doc.h']]], + ['mi_5fzalloc',['mi_zalloc',['../group__malloc.html#gafdd9d8bb2986e668ba9884f28af38000',1,'mimalloc-doc.h']]], + ['mi_5fzalloc_5faligned',['mi_zalloc_aligned',['../group__aligned.html#ga0cadbcf5b89a7b6fb171bc8df8734819',1,'mimalloc-doc.h']]], + ['mi_5fzalloc_5faligned_5fat',['mi_zalloc_aligned_at',['../group__aligned.html#ga5f8c2353766db522565e642fafd8a3f8',1,'mimalloc-doc.h']]], + ['mi_5fzalloc_5fsmall',['mi_zalloc_small',['../group__extended.html#ga220f29f40a44404b0061c15bc1c31152',1,'mimalloc-doc.h']]] +]; diff --git a/extlib/mimalloc/docs/search/functions_1.html b/extlib/mimalloc/docs/search/functions_1.html new file mode 100644 index 0000000..bfcf880 --- /dev/null +++ b/extlib/mimalloc/docs/search/functions_1.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/functions_1.js b/extlib/mimalloc/docs/search/functions_1.js new file mode 100644 index 0000000..06dbb19 --- /dev/null +++ b/extlib/mimalloc/docs/search/functions_1.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['void',['void',['../group__extended.html#gadc49452cc1634aa03ac83ffe9b97a19c',1,'mimalloc-doc.h']]] +]; diff --git a/extlib/mimalloc/docs/search/groups_0.html b/extlib/mimalloc/docs/search/groups_0.html new file mode 100644 index 0000000..194bb7b --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_0.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/groups_0.js b/extlib/mimalloc/docs/search/groups_0.js new file mode 100644 index 0000000..bbb4b54 --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_0.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['aligned_20allocation',['Aligned Allocation',['../group__aligned.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/groups_1.html b/extlib/mimalloc/docs/search/groups_1.html new file mode 100644 index 0000000..ed9b5c6 --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_1.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/groups_1.js b/extlib/mimalloc/docs/search/groups_1.js new file mode 100644 index 0000000..b258fac --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_1.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['basic_20allocation',['Basic Allocation',['../group__malloc.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/groups_2.html b/extlib/mimalloc/docs/search/groups_2.html new file mode 100644 index 0000000..17d4e06 --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_2.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/groups_2.js b/extlib/mimalloc/docs/search/groups_2.js new file mode 100644 index 0000000..2918576 --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_2.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['c_2b_2b_20wrappers',['C++ wrappers',['../group__cpp.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/groups_3.html b/extlib/mimalloc/docs/search/groups_3.html new file mode 100644 index 0000000..7d4a624 --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_3.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/groups_3.js b/extlib/mimalloc/docs/search/groups_3.js new file mode 100644 index 0000000..68c73db --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_3.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['extended_20functions',['Extended Functions',['../group__extended.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/groups_4.html b/extlib/mimalloc/docs/search/groups_4.html new file mode 100644 index 0000000..5e5ae2a --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_4.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/groups_4.js b/extlib/mimalloc/docs/search/groups_4.js new file mode 100644 index 0000000..e7e4093 --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_4.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['heap_20introspection',['Heap Introspection',['../group__analysis.html',1,'']]], + ['heap_20allocation',['Heap Allocation',['../group__heap.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/groups_5.html b/extlib/mimalloc/docs/search/groups_5.html new file mode 100644 index 0000000..fbd1460 --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_5.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/groups_5.js b/extlib/mimalloc/docs/search/groups_5.js new file mode 100644 index 0000000..4f00568 --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_5.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['posix',['Posix',['../group__posix.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/groups_6.html b/extlib/mimalloc/docs/search/groups_6.html new file mode 100644 index 0000000..277d80e --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_6.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/groups_6.js b/extlib/mimalloc/docs/search/groups_6.js new file mode 100644 index 0000000..2533cb9 --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_6.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['runtime_20options',['Runtime Options',['../group__options.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/groups_7.html b/extlib/mimalloc/docs/search/groups_7.html new file mode 100644 index 0000000..6a24e7c --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_7.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/groups_7.js b/extlib/mimalloc/docs/search/groups_7.js new file mode 100644 index 0000000..647887f --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_7.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['typed_20macros',['Typed Macros',['../group__typed.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/groups_8.html b/extlib/mimalloc/docs/search/groups_8.html new file mode 100644 index 0000000..81ac950 --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_8.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/groups_8.js b/extlib/mimalloc/docs/search/groups_8.js new file mode 100644 index 0000000..2b9b4ce --- /dev/null +++ b/extlib/mimalloc/docs/search/groups_8.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['zero_20initialized_20re_2dallocation',['Zero initialized re-allocation',['../group__zeroinit.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/mag_sel.png b/extlib/mimalloc/docs/search/mag_sel.png new file mode 100644 index 0000000000000000000000000000000000000000..39c0ed52a25dd9d080ee0d42ae6c6042bdfa04d7 GIT binary patch literal 465 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz6!2%?$TA$hhDVB6cUq=Rpjs4tz5?O(Kg=CK) zUj~NU84L`?eGCi_EEpJ?t}-xGu`@87+QPtK?83kxQ`TapwHK(CDaqU2h2ejD|C#+j z9%q3^WHAE+w=f7ZGR&GI0Tg5}@$_|Nf5gMiEhFgvHvB$N=!mC_V~EE2vzPXI9ZnEo zd+1zHor@dYLod2Y{ z@R$7$Z!PXTbY$|@#T!bMzm?`b<(R`cbw(gxJHzu zB$lLFB^RXvDF!10LknF)BV7aY5JN*NBMU1-b8Q0yD+2>vd*|CI8glbfGSez?Ylunu RoetE%;OXk;vd$@?2>>CYplSdB literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/search/nomatches.html b/extlib/mimalloc/docs/search/nomatches.html new file mode 100644 index 0000000..4377320 --- /dev/null +++ b/extlib/mimalloc/docs/search/nomatches.html @@ -0,0 +1,12 @@ + + + + + + + +
+
No Matches
+
+ + diff --git a/extlib/mimalloc/docs/search/pages_0.html b/extlib/mimalloc/docs/search/pages_0.html new file mode 100644 index 0000000..3d06b05 --- /dev/null +++ b/extlib/mimalloc/docs/search/pages_0.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/pages_0.js b/extlib/mimalloc/docs/search/pages_0.js new file mode 100644 index 0000000..33f3d05 --- /dev/null +++ b/extlib/mimalloc/docs/search/pages_0.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['building',['Building',['../build.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/pages_1.html b/extlib/mimalloc/docs/search/pages_1.html new file mode 100644 index 0000000..06f1e40 --- /dev/null +++ b/extlib/mimalloc/docs/search/pages_1.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/pages_1.js b/extlib/mimalloc/docs/search/pages_1.js new file mode 100644 index 0000000..0f2757c --- /dev/null +++ b/extlib/mimalloc/docs/search/pages_1.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['environment_20options',['Environment Options',['../environment.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/pages_2.html b/extlib/mimalloc/docs/search/pages_2.html new file mode 100644 index 0000000..703f781 --- /dev/null +++ b/extlib/mimalloc/docs/search/pages_2.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/pages_2.js b/extlib/mimalloc/docs/search/pages_2.js new file mode 100644 index 0000000..df03c8d --- /dev/null +++ b/extlib/mimalloc/docs/search/pages_2.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['overriding_20malloc',['Overriding Malloc',['../overrides.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/pages_3.html b/extlib/mimalloc/docs/search/pages_3.html new file mode 100644 index 0000000..299228a --- /dev/null +++ b/extlib/mimalloc/docs/search/pages_3.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/pages_3.js b/extlib/mimalloc/docs/search/pages_3.js new file mode 100644 index 0000000..d745403 --- /dev/null +++ b/extlib/mimalloc/docs/search/pages_3.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['performance',['Performance',['../bench.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/pages_4.html b/extlib/mimalloc/docs/search/pages_4.html new file mode 100644 index 0000000..021d277 --- /dev/null +++ b/extlib/mimalloc/docs/search/pages_4.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/pages_4.js b/extlib/mimalloc/docs/search/pages_4.js new file mode 100644 index 0000000..b47682a --- /dev/null +++ b/extlib/mimalloc/docs/search/pages_4.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['using_20the_20library',['Using the library',['../using.html',1,'']]] +]; diff --git a/extlib/mimalloc/docs/search/search.css b/extlib/mimalloc/docs/search/search.css new file mode 100644 index 0000000..10bd4b5 --- /dev/null +++ b/extlib/mimalloc/docs/search/search.css @@ -0,0 +1,273 @@ +/*---------------- Search Box */ + +#FSearchBox { + float: left; +} + +#MSearchBox { + white-space : nowrap; + float: none; + margin-top: 0px; + right: 0px; + width: 170px; + height: 24px; + z-index: 102; + display: inline; + position: absolute; +} + +#MSearchBox .left +{ + display:block; + position:absolute; + left:10px; + width:20px; + height:19px; + background:url('search_l.png') no-repeat; + background-position:right; +} + +#MSearchSelect { + display:block; + position:absolute; + width:20px; + height:19px; +} + +.left #MSearchSelect { + left:4px; +} + +.right #MSearchSelect { + right:5px; +} + +#MSearchField { + display:block; + position:absolute; + height:19px; + background:url('search_m.png') repeat-x; + border:none; + width:111px; + margin-left:20px; + padding-left:4px; + color: #909090; + outline: none; + font: 9pt Arial, Verdana, sans-serif; + -webkit-border-radius: 0px; +} + +#FSearchBox #MSearchField { + margin-left:15px; +} + +#MSearchBox .right { + display:block; + position:absolute; + right:10px; + top:0px; + width:20px; + height:19px; + background:url('search_r.png') no-repeat; + background-position:left; +} + +#MSearchClose { + display: none; + position: absolute; + top: 4px; + background : none; + border: none; + margin: 0px 4px 0px 0px; + padding: 0px 0px; + outline: none; +} + +.left #MSearchClose { + left: 6px; +} + +.right #MSearchClose { + right: 2px; +} + +.MSearchBoxActive #MSearchField { + color: #000000; +} + +/*---------------- Search filter selection */ + +#MSearchSelectWindow { + display: none; + position: absolute; + left: 0; top: 0; + border: 1px solid #4F5657; + background-color: #F2F3F3; + z-index: 10001; + padding-top: 4px; + padding-bottom: 4px; + -moz-border-radius: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); +} + +.SelectItem { + font: 8pt Arial, Verdana, sans-serif; + padding-left: 2px; + padding-right: 12px; + border: 0px; +} + +span.SelectionMark { + margin-right: 4px; + font-family: monospace; + outline-style: none; + text-decoration: none; +} + +a.SelectItem { + display: block; + outline-style: none; + color: #000000; + text-decoration: none; + padding-left: 6px; + padding-right: 12px; +} + +a.SelectItem:focus, +a.SelectItem:active { + color: #000000; + outline-style: none; + text-decoration: none; +} + +a.SelectItem:hover { + color: #FFFFFF; + background-color: #0F1010; + outline-style: none; + text-decoration: none; + cursor: pointer; + display: block; +} + +/*---------------- Search results window */ + +iframe#MSearchResults { + width: 60ex; + height: 15em; +} + +#MSearchResultsWindow { + display: none; + position: absolute; + left: 0; top: 0; + border: 1px solid #000; + background-color: #DADDDE; + z-index:10000; +} + +/* ----------------------------------- */ + + +#SRIndex { + clear:both; + padding-bottom: 15px; +} + +.SREntry { + font-size: 10pt; + padding-left: 1ex; +} + +.SRPage .SREntry { + font-size: 8pt; + padding: 1px 5px; +} + +body.SRPage { + margin: 5px 2px; +} + +.SRChildren { + padding-left: 3ex; padding-bottom: .5em +} + +.SRPage .SRChildren { + display: none; +} + +.SRSymbol { + font-weight: bold; + color: #121414; + font-family: Arial, Verdana, sans-serif; + text-decoration: none; + outline: none; +} + +a.SRScope { + display: block; + color: #121414; + font-family: Arial, Verdana, sans-serif; + text-decoration: none; + outline: none; +} + +a.SRSymbol:focus, a.SRSymbol:active, +a.SRScope:focus, a.SRScope:active { + text-decoration: underline; +} + +span.SRScope { + padding-left: 4px; +} + +.SRPage .SRStatus { + padding: 2px 5px; + font-size: 8pt; + font-style: italic; +} + +.SRResult { + display: none; +} + +DIV.searchresults { + margin-left: 10px; + margin-right: 10px; +} + +/*---------------- External search page results */ + +.searchresult { + background-color: #DFE1E2; +} + +.pages b { + color: white; + padding: 5px 5px 3px 5px; + background-image: url("../tab_a.png"); + background-repeat: repeat-x; + text-shadow: 0 1px 1px #000000; +} + +.pages { + line-height: 17px; + margin-left: 4px; + text-decoration: none; +} + +.hl { + font-weight: bold; +} + +#searchresults { + margin-bottom: 20px; +} + +.searchpages { + margin-top: 10px; +} + diff --git a/extlib/mimalloc/docs/search/search.js b/extlib/mimalloc/docs/search/search.js new file mode 100644 index 0000000..a554ab9 --- /dev/null +++ b/extlib/mimalloc/docs/search/search.js @@ -0,0 +1,814 @@ +/* + @licstart The following is the entire license notice for the + JavaScript code in this file. + + Copyright (C) 1997-2017 by Dimitri van Heesch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + @licend The above is the entire license notice + for the JavaScript code in this file + */ +function convertToId(search) +{ + var result = ''; + for (i=0;i do a search + { + this.Search(); + } + } + + this.OnSearchSelectKey = function(evt) + { + var e = (evt) ? evt : window.event; // for IE + if (e.keyCode==40 && this.searchIndex0) // Up + { + this.searchIndex--; + this.OnSelectItem(this.searchIndex); + } + else if (e.keyCode==13 || e.keyCode==27) + { + this.OnSelectItem(this.searchIndex); + this.CloseSelectionWindow(); + this.DOMSearchField().focus(); + } + return false; + } + + // --------- Actions + + // Closes the results window. + this.CloseResultsWindow = function() + { + this.DOMPopupSearchResultsWindow().style.display = 'none'; + this.DOMSearchClose().style.display = 'none'; + this.Activate(false); + } + + this.CloseSelectionWindow = function() + { + this.DOMSearchSelectWindow().style.display = 'none'; + } + + // Performs a search. + this.Search = function() + { + this.keyTimeout = 0; + + // strip leading whitespace + var searchValue = this.DOMSearchField().value.replace(/^ +/, ""); + + var code = searchValue.toLowerCase().charCodeAt(0); + var idxChar = searchValue.substr(0, 1).toLowerCase(); + if ( 0xD800 <= code && code <= 0xDBFF && searchValue > 1) // surrogate pair + { + idxChar = searchValue.substr(0, 2); + } + + var resultsPage; + var resultsPageWithSearch; + var hasResultsPage; + + var idx = indexSectionsWithContent[this.searchIndex].indexOf(idxChar); + if (idx!=-1) + { + var hexCode=idx.toString(16); + resultsPage = this.resultsPath + '/' + indexSectionNames[this.searchIndex] + '_' + hexCode + '.html'; + resultsPageWithSearch = resultsPage+'?'+escape(searchValue); + hasResultsPage = true; + } + else // nothing available for this search term + { + resultsPage = this.resultsPath + '/nomatches.html'; + resultsPageWithSearch = resultsPage; + hasResultsPage = false; + } + + window.frames.MSearchResults.location = resultsPageWithSearch; + var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow(); + + if (domPopupSearchResultsWindow.style.display!='block') + { + var domSearchBox = this.DOMSearchBox(); + this.DOMSearchClose().style.display = 'inline'; + if (this.insideFrame) + { + var domPopupSearchResults = this.DOMPopupSearchResults(); + domPopupSearchResultsWindow.style.position = 'relative'; + domPopupSearchResultsWindow.style.display = 'block'; + var width = document.body.clientWidth - 8; // the -8 is for IE :-( + domPopupSearchResultsWindow.style.width = width + 'px'; + domPopupSearchResults.style.width = width + 'px'; + } + else + { + var domPopupSearchResults = this.DOMPopupSearchResults(); + var left = getXPos(domSearchBox) + 150; // domSearchBox.offsetWidth; + var top = getYPos(domSearchBox) + 20; // domSearchBox.offsetHeight + 1; + domPopupSearchResultsWindow.style.display = 'block'; + left -= domPopupSearchResults.offsetWidth; + domPopupSearchResultsWindow.style.top = top + 'px'; + domPopupSearchResultsWindow.style.left = left + 'px'; + } + } + + this.lastSearchValue = searchValue; + this.lastResultsPage = resultsPage; + } + + // -------- Activation Functions + + // Activates or deactivates the search panel, resetting things to + // their default values if necessary. + this.Activate = function(isActive) + { + if (isActive || // open it + this.DOMPopupSearchResultsWindow().style.display == 'block' + ) + { + this.DOMSearchBox().className = 'MSearchBoxActive'; + + var searchField = this.DOMSearchField(); + + if (searchField.value == this.searchLabel) // clear "Search" term upon entry + { + searchField.value = ''; + this.searchActive = true; + } + } + else if (!isActive) // directly remove the panel + { + this.DOMSearchBox().className = 'MSearchBoxInactive'; + this.DOMSearchField().value = this.searchLabel; + this.searchActive = false; + this.lastSearchValue = '' + this.lastResultsPage = ''; + } + } +} + +// ----------------------------------------------------------------------- + +// The class that handles everything on the search results page. +function SearchResults(name) +{ + // The number of matches from the last run of . + this.lastMatchCount = 0; + this.lastKey = 0; + this.repeatOn = false; + + // Toggles the visibility of the passed element ID. + this.FindChildElement = function(id) + { + var parentElement = document.getElementById(id); + var element = parentElement.firstChild; + + while (element && element!=parentElement) + { + if (element.nodeName == 'DIV' && element.className == 'SRChildren') + { + return element; + } + + if (element.nodeName == 'DIV' && element.hasChildNodes()) + { + element = element.firstChild; + } + else if (element.nextSibling) + { + element = element.nextSibling; + } + else + { + do + { + element = element.parentNode; + } + while (element && element!=parentElement && !element.nextSibling); + + if (element && element!=parentElement) + { + element = element.nextSibling; + } + } + } + } + + this.Toggle = function(id) + { + var element = this.FindChildElement(id); + if (element) + { + if (element.style.display == 'block') + { + element.style.display = 'none'; + } + else + { + element.style.display = 'block'; + } + } + } + + // Searches for the passed string. If there is no parameter, + // it takes it from the URL query. + // + // Always returns true, since other documents may try to call it + // and that may or may not be possible. + this.Search = function(search) + { + if (!search) // get search word from URL + { + search = window.location.search; + search = search.substring(1); // Remove the leading '?' + search = unescape(search); + } + + search = search.replace(/^ +/, ""); // strip leading spaces + search = search.replace(/ +$/, ""); // strip trailing spaces + search = search.toLowerCase(); + search = convertToId(search); + + var resultRows = document.getElementsByTagName("div"); + var matches = 0; + + var i = 0; + while (i < resultRows.length) + { + var row = resultRows.item(i); + if (row.className == "SRResult") + { + var rowMatchName = row.id.toLowerCase(); + rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); // strip 'sr123_' + + if (search.length<=rowMatchName.length && + rowMatchName.substr(0, search.length)==search) + { + row.style.display = 'block'; + matches++; + } + else + { + row.style.display = 'none'; + } + } + i++; + } + document.getElementById("Searching").style.display='none'; + if (matches == 0) // no results + { + document.getElementById("NoMatches").style.display='block'; + } + else // at least one result + { + document.getElementById("NoMatches").style.display='none'; + } + this.lastMatchCount = matches; + return true; + } + + // return the first item with index index or higher that is visible + this.NavNext = function(index) + { + var focusItem; + while (1) + { + var focusName = 'Item'+index; + focusItem = document.getElementById(focusName); + if (focusItem && focusItem.parentNode.parentNode.style.display=='block') + { + break; + } + else if (!focusItem) // last element + { + break; + } + focusItem=null; + index++; + } + return focusItem; + } + + this.NavPrev = function(index) + { + var focusItem; + while (1) + { + var focusName = 'Item'+index; + focusItem = document.getElementById(focusName); + if (focusItem && focusItem.parentNode.parentNode.style.display=='block') + { + break; + } + else if (!focusItem) // last element + { + break; + } + focusItem=null; + index--; + } + return focusItem; + } + + this.ProcessKeys = function(e) + { + if (e.type == "keydown") + { + this.repeatOn = false; + this.lastKey = e.keyCode; + } + else if (e.type == "keypress") + { + if (!this.repeatOn) + { + if (this.lastKey) this.repeatOn = true; + return false; // ignore first keypress after keydown + } + } + else if (e.type == "keyup") + { + this.lastKey = 0; + this.repeatOn = false; + } + return this.lastKey!=0; + } + + this.Nav = function(evt,itemIndex) + { + var e = (evt) ? evt : window.event; // for IE + if (e.keyCode==13) return true; + if (!this.ProcessKeys(e)) return false; + + if (this.lastKey==38) // Up + { + var newIndex = itemIndex-1; + var focusItem = this.NavPrev(newIndex); + if (focusItem) + { + var child = this.FindChildElement(focusItem.parentNode.parentNode.id); + if (child && child.style.display == 'block') // children visible + { + var n=0; + var tmpElem; + while (1) // search for last child + { + tmpElem = document.getElementById('Item'+newIndex+'_c'+n); + if (tmpElem) + { + focusItem = tmpElem; + } + else // found it! + { + break; + } + n++; + } + } + } + if (focusItem) + { + focusItem.focus(); + } + else // return focus to search field + { + parent.document.getElementById("MSearchField").focus(); + } + } + else if (this.lastKey==40) // Down + { + var newIndex = itemIndex+1; + var focusItem; + var item = document.getElementById('Item'+itemIndex); + var elem = this.FindChildElement(item.parentNode.parentNode.id); + if (elem && elem.style.display == 'block') // children visible + { + focusItem = document.getElementById('Item'+itemIndex+'_c0'); + } + if (!focusItem) focusItem = this.NavNext(newIndex); + if (focusItem) focusItem.focus(); + } + else if (this.lastKey==39) // Right + { + var item = document.getElementById('Item'+itemIndex); + var elem = this.FindChildElement(item.parentNode.parentNode.id); + if (elem) elem.style.display = 'block'; + } + else if (this.lastKey==37) // Left + { + var item = document.getElementById('Item'+itemIndex); + var elem = this.FindChildElement(item.parentNode.parentNode.id); + if (elem) elem.style.display = 'none'; + } + else if (this.lastKey==27) // Escape + { + parent.searchBox.CloseResultsWindow(); + parent.document.getElementById("MSearchField").focus(); + } + else if (this.lastKey==13) // Enter + { + return true; + } + return false; + } + + this.NavChild = function(evt,itemIndex,childIndex) + { + var e = (evt) ? evt : window.event; // for IE + if (e.keyCode==13) return true; + if (!this.ProcessKeys(e)) return false; + + if (this.lastKey==38) // Up + { + if (childIndex>0) + { + var newIndex = childIndex-1; + document.getElementById('Item'+itemIndex+'_c'+newIndex).focus(); + } + else // already at first child, jump to parent + { + document.getElementById('Item'+itemIndex).focus(); + } + } + else if (this.lastKey==40) // Down + { + var newIndex = childIndex+1; + var elem = document.getElementById('Item'+itemIndex+'_c'+newIndex); + if (!elem) // last child, jump to parent next parent + { + elem = this.NavNext(itemIndex+1); + } + if (elem) + { + elem.focus(); + } + } + else if (this.lastKey==27) // Escape + { + parent.searchBox.CloseResultsWindow(); + parent.document.getElementById("MSearchField").focus(); + } + else if (this.lastKey==13) // Enter + { + return true; + } + return false; + } +} + +function setKeyActions(elem,action) +{ + elem.setAttribute('onkeydown',action); + elem.setAttribute('onkeypress',action); + elem.setAttribute('onkeyup',action); +} + +function setClassAttr(elem,attr) +{ + elem.setAttribute('class',attr); + elem.setAttribute('className',attr); +} + +function createResults() +{ + var results = document.getElementById("SRResults"); + for (var e=0; e(R!W8j_r#qQ#gnr4kAxdU#F0+OBry$Z+ z_0PMi;P|#{d%mw(dnw=jM%@$onTJa%@6Nm3`;2S#nwtVFJI#`U@2Q@@JCCctagvF- z8H=anvo~dTmJ2YA%wA6IHRv%{vxvUm|R)kgZeo zmX%Zb;mpflGZdXCTAgit`||AFzkI#z&(3d4(htA?U2FOL4WF6wY&TB#n3n*I4+hl| z*NBpo#FA92vEu822WQ%mvv4FO#qs` BFGc_W literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/search/search_r.png b/extlib/mimalloc/docs/search/search_r.png new file mode 100644 index 0000000000000000000000000000000000000000..1af5d21ee13e070d7600f1c4657fde843b953a69 GIT binary patch literal 553 zcmeAS@N?(olHy`uVBq!ia0vp^LO?9c!2%@BXHTsJQY`6?zK#qG8~eHcB(ehe3dtTp zz6=bxGZ+|(`xqD=STHa&U1eaXVrO7DwS|Gf*oA>XrmV$GYcEhOQT(QLuS{~ooZ2P@v=Xc@RKW@Irliv8_;wroU0*)0O?temdsA~70jrdux+`@W7 z-N(<(C)L?hOO?KV{>8(jC{hpKsws)#Fh zvsO>IB+gb@b+rGWaO&!a9Z{!U+fV*s7TS>fdt&j$L%^U@Epd$~Nl7e8wMs5Z1yT$~ z28I^8hDN#u<{^fLRz?<9hUVG^237_Jy7tbuQ8eV{r(~v8;?@w8^gA7>fx*+&&t;uc GLK6VEQpiUD literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/search/searchdata.js b/extlib/mimalloc/docs/search/searchdata.js new file mode 100644 index 0000000..dd31068 --- /dev/null +++ b/extlib/mimalloc/docs/search/searchdata.js @@ -0,0 +1,39 @@ +var indexSectionsWithContent = +{ + 0: "_abcehmoprtuz", + 1: "m", + 2: "m", + 3: "bcru", + 4: "m", + 5: "m", + 6: "_m", + 7: "abcehprtz", + 8: "beopu" +}; + +var indexSectionNames = +{ + 0: "all", + 1: "classes", + 2: "functions", + 3: "variables", + 4: "typedefs", + 5: "enums", + 6: "enumvalues", + 7: "groups", + 8: "pages" +}; + +var indexSectionLabels = +{ + 0: "All", + 1: "Data Structures", + 2: "Functions", + 3: "Variables", + 4: "Typedefs", + 5: "Enumerations", + 6: "Enumerator", + 7: "Modules", + 8: "Pages" +}; + diff --git a/extlib/mimalloc/docs/search/typedefs_0.html b/extlib/mimalloc/docs/search/typedefs_0.html new file mode 100644 index 0000000..3848b20 --- /dev/null +++ b/extlib/mimalloc/docs/search/typedefs_0.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/typedefs_0.js b/extlib/mimalloc/docs/search/typedefs_0.js new file mode 100644 index 0000000..44a0a6c --- /dev/null +++ b/extlib/mimalloc/docs/search/typedefs_0.js @@ -0,0 +1,8 @@ +var searchData= +[ + ['mi_5fblock_5fvisit_5ffun',['mi_block_visit_fun',['../group__analysis.html#gadfa01e2900f0e5d515ad5506b26f6d65',1,'mimalloc-doc.h']]], + ['mi_5fdeferred_5ffree_5ffun',['mi_deferred_free_fun',['../group__extended.html#ga299dae78d25ce112e384a98b7309c5be',1,'mimalloc-doc.h']]], + ['mi_5ferror_5ffun',['mi_error_fun',['../group__extended.html#ga251d369cda3f1c2a955c555486ed90e5',1,'mimalloc-doc.h']]], + ['mi_5fheap_5ft',['mi_heap_t',['../group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2',1,'mimalloc-doc.h']]], + ['mi_5foutput_5ffun',['mi_output_fun',['../group__extended.html#gad823d23444a4b77a40f66bf075a98a0c',1,'mimalloc-doc.h']]] +]; diff --git a/extlib/mimalloc/docs/search/typedefs_1.html b/extlib/mimalloc/docs/search/typedefs_1.html new file mode 100644 index 0000000..c8a0268 --- /dev/null +++ b/extlib/mimalloc/docs/search/typedefs_1.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/typedefs_1.js b/extlib/mimalloc/docs/search/typedefs_1.js new file mode 100644 index 0000000..ecccb16 --- /dev/null +++ b/extlib/mimalloc/docs/search/typedefs_1.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['heartbeat',['heartbeat',['../group__extended.html#ga411f6e94394a2400aa460c796beff8d8',1,'mimalloc-doc.h']]] +]; diff --git a/extlib/mimalloc/docs/search/typedefs_2.html b/extlib/mimalloc/docs/search/typedefs_2.html new file mode 100644 index 0000000..86a9195 --- /dev/null +++ b/extlib/mimalloc/docs/search/typedefs_2.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/typedefs_2.js b/extlib/mimalloc/docs/search/typedefs_2.js new file mode 100644 index 0000000..2af0607 --- /dev/null +++ b/extlib/mimalloc/docs/search/typedefs_2.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['mi_5fblock_5fvisit_5ffun',['mi_block_visit_fun',['../group__analysis.html#gadfa01e2900f0e5d515ad5506b26f6d65',1,'mimalloc-doc.h']]], + ['mi_5fheap_5ft',['mi_heap_t',['../group__heap.html#ga34a47cde5a5b38c29f1aa3c5e76943c2',1,'mimalloc-doc.h']]] +]; diff --git a/extlib/mimalloc/docs/search/variables_0.html b/extlib/mimalloc/docs/search/variables_0.html new file mode 100644 index 0000000..12104bc --- /dev/null +++ b/extlib/mimalloc/docs/search/variables_0.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/variables_0.js b/extlib/mimalloc/docs/search/variables_0.js new file mode 100644 index 0000000..330c800 --- /dev/null +++ b/extlib/mimalloc/docs/search/variables_0.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['block_5fsize',['block_size',['../group__analysis.html#a332a6c14d736a99699d5453a1cb04b41',1,'mi_heap_area_t']]], + ['blocks',['blocks',['../group__analysis.html#ae0085e6e1cf059a4eb7767e30e9991b8',1,'mi_heap_area_t']]] +]; diff --git a/extlib/mimalloc/docs/search/variables_1.html b/extlib/mimalloc/docs/search/variables_1.html new file mode 100644 index 0000000..b784017 --- /dev/null +++ b/extlib/mimalloc/docs/search/variables_1.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/variables_1.js b/extlib/mimalloc/docs/search/variables_1.js new file mode 100644 index 0000000..af76e9c --- /dev/null +++ b/extlib/mimalloc/docs/search/variables_1.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['committed',['committed',['../group__analysis.html#ab47526df656d8837ec3e97f11b83f835',1,'mi_heap_area_t']]] +]; diff --git a/extlib/mimalloc/docs/search/variables_2.html b/extlib/mimalloc/docs/search/variables_2.html new file mode 100644 index 0000000..0cb98d3 --- /dev/null +++ b/extlib/mimalloc/docs/search/variables_2.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/variables_2.js b/extlib/mimalloc/docs/search/variables_2.js new file mode 100644 index 0000000..304ad43 --- /dev/null +++ b/extlib/mimalloc/docs/search/variables_2.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['reserved',['reserved',['../group__analysis.html#ae848a3e6840414891035423948ca0383',1,'mi_heap_area_t']]] +]; diff --git a/extlib/mimalloc/docs/search/variables_3.html b/extlib/mimalloc/docs/search/variables_3.html new file mode 100644 index 0000000..1e83bf5 --- /dev/null +++ b/extlib/mimalloc/docs/search/variables_3.html @@ -0,0 +1,30 @@ + + + + + + + + + +
+
Loading...
+
+ +
Searching...
+
No Matches
+ +
+ + diff --git a/extlib/mimalloc/docs/search/variables_3.js b/extlib/mimalloc/docs/search/variables_3.js new file mode 100644 index 0000000..c889d4f --- /dev/null +++ b/extlib/mimalloc/docs/search/variables_3.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['used',['used',['../group__analysis.html#ab820302c5cd0df133eb8e51650a008b4',1,'mi_heap_area_t']]] +]; diff --git a/extlib/mimalloc/docs/splitbar.png b/extlib/mimalloc/docs/splitbar.png new file mode 100644 index 0000000000000000000000000000000000000000..cce10a788e2037b1e411816b4088ae5b59aedc58 GIT binary patch literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^Yzz!63>-{AmhX=Jf@Vhihj(260Q#q zm#`lY<52E;G2^C_ula>*XBAc)oz%rC#?6!xe0RfyL}gv;1&Ol~)*ZY$_sJgZeKz0o z6SidBI)69MYPRpIUu8Ar!P8hB>^zy2NJTGuw%p8X|7-W={|X7w(?9(%ei|E(^5d~WgP^y}LjPJg?%*waCU4D{D|{|*=Xqz8YtwgH2H N!PC{xWt~$(69AdfaG3xA literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/sync_off.png b/extlib/mimalloc/docs/sync_off.png new file mode 100644 index 0000000000000000000000000000000000000000..f3be5edaf093ee6824216091197355f41726802a GIT binary patch literal 851 zcmV-Z1FZasP)26&VNSQlUevHt?R%pq#W(sRc5-8Me z)!`L4xp#?V?d!w0`LXthGw~I*-n+C0Se$hoc49B>JPw3Sh2IifXg5C^aqJ zjIJYJ2Cx8hu!G{IIfJ7j3&3tkd^|uzi(OGC`+;cJO{4)b#aK@3!6zSwK5uDkpw=@JK#R-eqWk!FG#xqexA|vl0Eh91r5oE7AgZQA@6M2QCIEKJ zQa(#iUi|SVPNy-BcS|WVxqjy^%Mug$Y@ObLmQ(iq z8Gh{VOA9t9^s>9GgEC*p&C8>w>jY`3sYd(2H{Wva_AP)}GTd&i4PG&x{o~^_9cyD` z=$`czpDxjG*odD#Wlv2lBM%=L?bx_DfQZcRbe5j#8hhtjhIIz3<>9ezfBTOcWz9!@ zK5qVU6=0T?IXTpOy>xbT;D7xZ!0uBsALu0)x}ZWgq0^~{E->!*N4yhaVq=+p8z2yP z3t)Fwla(1DTl_9Q1XuuXf(Q74dD*=XUjrvuSDUqSYr?UX--5{}NsmQ@vB+bVB#|X8ofmTFqCG3{0EGYdU zcOO=(-OYX5pjy~kHp&H|zdQI~_!3e*1j{}+liH%Q{mx~sbMG`GD!yKRAH4UBoZmU; z-gADhmcpW9fM}fJQ?X4HiS@!JUJI}IOZ+J&0V1(PNsuZ|2zT%vAeuK~MD&X1fGJ`C z!c>SskqfX4iNYglMYs6T_@fmtMw7U;g3gVXLtGU{q9xcVhNH|1V7a~(t@?v4Ej>fe zu11~=um;p%uZw^58GI?S0V4SzApyW7#=hi4KNMf9k4PoR5-kAXEEY?^bKn45x9=>T7ENmQ=Q-lq*9&u+ilL2v?~Rlo(q|O1E(B$}olb`HOA4sw$~# z+($#xe$5`Yn})rOi@#@QW^!$Ki1N>>0aG~Cn_8#8w0+Gj9RB;&aAs(qEWoO*OLbtg+1?t@qG$4VlipCZyN14= zQ$Q#tCMTJC`AP<%jfFtqf0C1u_@r2!e})})@e(6fFSD=^(3@VU1i#2lgYh3-1qBTB zeM5R$n)ZC?*m3UtaR&%Rrqju9*MHUSeSSYjPxUZ%{~llpuUeu}z!VeX<21LmGxqS2 z_8b=<4`8CVsoA+(+ah(Yu2D4uwLDMMrw^Dyb<%zK2#>v9Zr{8Cgd#UDj}DKA-m_=% zO-}X2JJpiHNs)jBVmF2?Wk0ilSEizBcw&;0xXKqPlnvC?(S8=`!Sftowa$yarD}4!=od)gT+T} zb#;P;msNmpzXu1V_7o6`NHZ{A1)^S5h}2*qQ3?I3T8aC?PPO$02=@-;ZLA3F(IQ0v O00007zQd7m VI^To$RzRZ|JYD@<);T3K0RR|MDqH{n literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/tab_b.png b/extlib/mimalloc/docs/tab_b.png new file mode 100644 index 0000000000000000000000000000000000000000..6b37547b358f4f2faeafa2549fa3aa5b618a1b71 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfy!2~3aiye;!Qq`U=jv*C{Z|B-`9Wsz{n|z=9 z%1OSX!8nx73d5GPgg&ebxsLQ06~vR2LJ#7 literal 0 HcmV?d00001 diff --git a/extlib/mimalloc/docs/tab_h.png b/extlib/mimalloc/docs/tab_h.png new file mode 100644 index 0000000000000000000000000000000000000000..1facf3ed2f43e21e3861a4d7f2181d26239b7d19 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfy!2~3aiye;!Ql*|Qjv*C{Z|6F49WoGbo%~;M zHdmRl>#J2qw{GJUUVL@YjngIC_wM_AkTHJ$G0iXW+IjKYyN)eeWwBq}@4WcsWW5t# zk6GM(`M6b4wNHq@%sLN0?7d$$-tC=OVX0$G@1jo4=c!DxK(M>BO-uXyeV3 dQ + + + + + + +mi-malloc: Using the library + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+
mi-malloc +  1.6 +
+
+ + + + + + +
+
+
+ + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
Using the library
+
+
+

Build

+

The preferred usage is including <mimalloc.h>, linking with the shared- or static library, and using the mi_malloc API exclusively for allocation. For example,

gcc -o myprogram -lmimalloc myfile.c

mimalloc uses only safe OS calls (mmap and VirtualAlloc) and can co-exist with other allocators linked to the same program. If you use cmake, you can simply use:

find_package(mimalloc 1.0 REQUIRED)

in your CMakeLists.txt to find a locally installed mimalloc. Then use either:

target_link_libraries(myapp PUBLIC mimalloc)

to link with the shared (dynamic) library, or:

target_link_libraries(myapp PUBLIC mimalloc-static)

to link with the static library. See test\CMakeLists.txt for an example.

+

C++

+

For best performance in C++ programs, it is also recommended to override the global new and delete operators. For convience, mimalloc provides mimalloc-new-delete.h which does this for you – just include it in a single(!) source file in your project.

+

In C++, mimalloc also provides the mi_stl_allocator struct which implements the std::allocator interface. For example:

std::vector<some_struct, mi_stl_allocator<some_struct>> vec;
vec.push_back(some_struct());

Statistics

+

You can pass environment variables to print verbose messages (MIMALLOC_VERBOSE=1) and statistics (MIMALLOC_SHOW_STATS=1) (in the debug version):

> env MIMALLOC_SHOW_STATS=1 ./cfrac 175451865205073170563711388363
175451865205073170563711388363 = 374456281610909315237213 * 468551
heap stats: peak total freed unit
normal 2: 16.4 kb 17.5 mb 17.5 mb 16 b ok
normal 3: 16.3 kb 15.2 mb 15.2 mb 24 b ok
normal 4: 64 b 4.6 kb 4.6 kb 32 b ok
normal 5: 80 b 118.4 kb 118.4 kb 40 b ok
normal 6: 48 b 48 b 48 b 48 b ok
normal 17: 960 b 960 b 960 b 320 b ok
heap stats: peak total freed unit
normal: 33.9 kb 32.8 mb 32.8 mb 1 b ok
huge: 0 b 0 b 0 b 1 b ok
total: 33.9 kb 32.8 mb 32.8 mb 1 b ok
malloc requested: 32.8 mb
committed: 58.2 kb 58.2 kb 58.2 kb 1 b ok
reserved: 2.0 mb 2.0 mb 2.0 mb 1 b ok
reset: 0 b 0 b 0 b 1 b ok
segments: 1 1 1
-abandoned: 0
pages: 6 6 6
-abandoned: 0
mmaps: 3
mmap fast: 0
mmap slow: 1
threads: 0
elapsed: 2.022s
process: user: 1.781s, system: 0.016s, faults: 756, reclaims: 0, rss: 2.7 mb

The above model of using the mi_ prefixed API is not always possible though in existing programs that already use the standard malloc interface, and another option is to override the standard malloc interface completely and redirect all calls to the mimalloc library instead.

+

See Overriding Malloc for more info.

+
+
+
+ + + + diff --git a/extlib/mimalloc/ide/vs2017/mimalloc-override-test.vcxproj b/extlib/mimalloc/ide/vs2017/mimalloc-override-test.vcxproj new file mode 100644 index 0000000..faaa00e --- /dev/null +++ b/extlib/mimalloc/ide/vs2017/mimalloc-override-test.vcxproj @@ -0,0 +1,190 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {FEF7868F-750E-4C21-A04D-22707CC66879} + mimalloc-override-test + 10.0.17134.0 + mimalloc-override-test + + + + Application + true + v141 + + + Application + false + v141 + true + + + Application + true + v141 + + + Application + false + v141 + true + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + + Level3 + Disabled + true + true + ..\..\include + MultiThreadedDebugDLL + false + Default + false + + + Console + kernel32.lib;%(AdditionalDependencies) + + + + + + + + + + Level3 + Disabled + true + true + ..\..\include + MultiThreadedDebugDLL + Sync + Default + false + + + Console + + + kernel32.lib;%(AdditionalDependencies) + + + + + + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + _MBCS;%(PreprocessorDefinitions);NDEBUG + MultiThreadedDLL + + + true + true + Console + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + _MBCS;%(PreprocessorDefinitions);NDEBUG + MultiThreadedDLL + + + true + true + Console + + + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + {abb5eae7-b3e6-432e-b636-333449892ea7} + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2017/mimalloc-override-test.vcxproj.filters b/extlib/mimalloc/ide/vs2017/mimalloc-override-test.vcxproj.filters new file mode 100644 index 0000000..eb5e70b --- /dev/null +++ b/extlib/mimalloc/ide/vs2017/mimalloc-override-test.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2017/mimalloc-override.vcxproj b/extlib/mimalloc/ide/vs2017/mimalloc-override.vcxproj new file mode 100644 index 0000000..990d6ca --- /dev/null +++ b/extlib/mimalloc/ide/vs2017/mimalloc-override.vcxproj @@ -0,0 +1,254 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {ABB5EAE7-B3E6-432E-B636-333449892EA7} + mimalloc-override + 10.0.17134.0 + mimalloc-override + + + + DynamicLibrary + true + v141 + + + DynamicLibrary + false + v141 + + + DynamicLibrary + true + v141 + + + DynamicLibrary + false + v141 + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .dll + mimalloc-override + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .dll + mimalloc-override + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .dll + mimalloc-override + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .dll + mimalloc-override + + + + Level3 + Disabled + true + true + ../../include + _CRT_SECURE_NO_WARNINGS;MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions); + MultiThreadedDebugDLL + false + Default + + + $(ProjectDir)\..\..\bin\mimalloc-redirect32.lib;%(AdditionalDependencies) + + + + + Default + false + + + COPY /Y $(ProjectDir)..\..\bin\mimalloc-redirect32.dll $(OutputPath) + + + Copy mimalloc-redirect32.dll to the output directory + + + + + Level3 + Disabled + true + true + ../../include + _CRT_SECURE_NO_WARNINGS;MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions); + MultiThreadedDebugDLL + false + Default + + + $(ProjectDir)\..\..\bin\mimalloc-redirect.lib;bcrypt.lib;%(AdditionalDependencies) + + + + + Default + false + + + COPY /Y $(ProjectDir)..\..\bin\mimalloc-redirect.dll $(OutputPath) + + + copy mimalloc-redirect.dll to the output directory + + + + + Level3 + MaxSpeed + true + true + true + ../../include + _CRT_SECURE_NO_WARNINGS;MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);NDEBUG + AssemblyAndSourceCode + $(IntDir) + false + MultiThreadedDLL + Default + false + + + true + true + $(ProjectDir)\..\..\bin\mimalloc-redirect32.lib;%(AdditionalDependencies) + + + Default + false + + + COPY /Y $(ProjectDir)..\..\bin\mimalloc-redirect32.dll $(OutputPath) + + + Copy mimalloc-redirect32.dll to the output directory + + + + + Level3 + MaxSpeed + true + true + true + ../../include + _CRT_SECURE_NO_WARNINGS;MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);NDEBUG + AssemblyAndSourceCode + $(IntDir) + false + MultiThreadedDLL + Default + false + + + true + true + $(ProjectDir)\..\..\bin\mimalloc-redirect.lib;bcrypt.lib;%(AdditionalDependencies) + + + Default + false + + + COPY /Y $(ProjectDir)..\..\bin\mimalloc-redirect.dll $(OutputPath) + + + copy mimalloc-redirect.dll to the output directory + + + + + + + + + + + + + false + false + false + false + + + true + true + true + true + + + + + + + + + + + true + true + true + true + + + + + + + + + + diff --git a/extlib/mimalloc/ide/vs2017/mimalloc-override.vcxproj.filters b/extlib/mimalloc/ide/vs2017/mimalloc-override.vcxproj.filters new file mode 100644 index 0000000..0265265 --- /dev/null +++ b/extlib/mimalloc/ide/vs2017/mimalloc-override.vcxproj.filters @@ -0,0 +1,80 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + diff --git a/extlib/mimalloc/ide/vs2017/mimalloc-test-stress.vcxproj b/extlib/mimalloc/ide/vs2017/mimalloc-test-stress.vcxproj new file mode 100644 index 0000000..b8267d0 --- /dev/null +++ b/extlib/mimalloc/ide/vs2017/mimalloc-test-stress.vcxproj @@ -0,0 +1,159 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {FEF7958F-750E-4C21-A04D-22707CC66878} + mimalloc-test-stress + 10.0.17134.0 + mimalloc-test-stress + + + + Application + true + v141 + + + Application + false + v141 + true + + + Application + true + v141 + + + Application + false + v141 + true + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + + Level3 + Disabled + true + true + ..\..\include + + + Console + + + + + Level3 + Disabled + true + true + ..\..\include + + + Console + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + %(PreprocessorDefinitions);NDEBUG + + + true + true + Console + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + %(PreprocessorDefinitions);NDEBUG + + + true + true + Console + + + + + false + false + false + false + + + + + {abb5eae7-b3e6-432e-b636-333449892ea6} + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2017/mimalloc-test-stress.vcxproj.filters b/extlib/mimalloc/ide/vs2017/mimalloc-test-stress.vcxproj.filters new file mode 100644 index 0000000..7c5239e --- /dev/null +++ b/extlib/mimalloc/ide/vs2017/mimalloc-test-stress.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2017/mimalloc-test.vcxproj b/extlib/mimalloc/ide/vs2017/mimalloc-test.vcxproj new file mode 100644 index 0000000..27c7bb6 --- /dev/null +++ b/extlib/mimalloc/ide/vs2017/mimalloc-test.vcxproj @@ -0,0 +1,158 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {FEF7858F-750E-4C21-A04D-22707CC66878} + mimalloctest + 10.0.17134.0 + mimalloc-test + + + + Application + true + v141 + + + Application + false + v141 + true + + + Application + true + v141 + + + Application + false + v141 + true + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + + Level3 + Disabled + true + true + ..\..\include + stdcpp17 + + + Console + + + + + Level3 + Disabled + true + true + ..\..\include + stdcpp17 + + + Console + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + _MBCS;%(PreprocessorDefinitions);NDEBUG + stdcpp17 + + + true + true + Console + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + _MBCS;%(PreprocessorDefinitions);NDEBUG + stdcpp17 + + + true + true + Console + + + + + {abb5eae7-b3e6-432e-b636-333449892ea6} + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2017/mimalloc-test.vcxproj.filters b/extlib/mimalloc/ide/vs2017/mimalloc-test.vcxproj.filters new file mode 100644 index 0000000..fca75e1 --- /dev/null +++ b/extlib/mimalloc/ide/vs2017/mimalloc-test.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2017/mimalloc.sln b/extlib/mimalloc/ide/vs2017/mimalloc.sln new file mode 100644 index 0000000..aeab6b8 --- /dev/null +++ b/extlib/mimalloc/ide/vs2017/mimalloc.sln @@ -0,0 +1,71 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2016 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mimalloc", "mimalloc.vcxproj", "{ABB5EAE7-B3E6-432E-B636-333449892EA6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mimalloc-test", "mimalloc-test.vcxproj", "{FEF7858F-750E-4C21-A04D-22707CC66878}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mimalloc-override", "mimalloc-override.vcxproj", "{ABB5EAE7-B3E6-432E-B636-333449892EA7}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mimalloc-override-test", "mimalloc-override-test.vcxproj", "{FEF7868F-750E-4C21-A04D-22707CC66879}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mimalloc-test-stress", "mimalloc-test-stress.vcxproj", "{FEF7958F-750E-4C21-A04D-22707CC66878}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x64.ActiveCfg = Debug|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x64.Build.0 = Debug|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x86.ActiveCfg = Debug|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x86.Build.0 = Debug|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x64.ActiveCfg = Release|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x64.Build.0 = Release|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x86.ActiveCfg = Release|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x86.Build.0 = Release|Win32 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x64.ActiveCfg = Debug|x64 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x64.Build.0 = Debug|x64 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x86.ActiveCfg = Debug|Win32 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x86.Build.0 = Debug|Win32 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x64.ActiveCfg = Release|x64 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x64.Build.0 = Release|x64 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x86.ActiveCfg = Release|Win32 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x86.Build.0 = Release|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x64.ActiveCfg = Debug|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x64.Build.0 = Debug|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x86.ActiveCfg = Debug|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x86.Build.0 = Debug|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x64.ActiveCfg = Release|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x64.Build.0 = Release|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x86.ActiveCfg = Release|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x86.Build.0 = Release|Win32 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x64.ActiveCfg = Debug|x64 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x64.Build.0 = Debug|x64 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x86.ActiveCfg = Debug|Win32 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x86.Build.0 = Debug|Win32 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x64.ActiveCfg = Release|x64 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x64.Build.0 = Release|x64 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x86.ActiveCfg = Release|Win32 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x86.Build.0 = Release|Win32 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x64.ActiveCfg = Debug|x64 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x64.Build.0 = Debug|x64 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x86.ActiveCfg = Debug|Win32 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x86.Build.0 = Debug|Win32 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x64.ActiveCfg = Release|x64 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x64.Build.0 = Release|x64 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x86.ActiveCfg = Release|Win32 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4297F93D-486A-4243-995F-7D32F59AE82A} + EndGlobalSection +EndGlobal diff --git a/extlib/mimalloc/ide/vs2017/mimalloc.vcxproj b/extlib/mimalloc/ide/vs2017/mimalloc.vcxproj new file mode 100644 index 0000000..770a87b --- /dev/null +++ b/extlib/mimalloc/ide/vs2017/mimalloc.vcxproj @@ -0,0 +1,260 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {ABB5EAE7-B3E6-432E-B636-333449892EA6} + mimalloc + 10.0.17134.0 + mimalloc + + + + StaticLibrary + true + v141 + + + StaticLibrary + false + v141 + true + + + StaticLibrary + true + v141 + + + StaticLibrary + false + v141 + true + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .lib + mimalloc-static + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .lib + mimalloc-static + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .lib + mimalloc-static + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .lib + mimalloc-static + + + false + + + false + + + false + + + false + + + + Level3 + Disabled + true + true + ../../include + _CRT_SECURE_NO_WARNINGS;MI_DEBUG=3;%(PreprocessorDefinitions); + CompileAsCpp + false + stdcpp17 + + + + + + + + + + + Level4 + Disabled + true + true + ../../include + _CRT_SECURE_NO_WARNINGS;MI_DEBUG=3;%(PreprocessorDefinitions); + CompileAsCpp + false + stdcpp17 + + + + + + + + + + + + + + + + + + + Level3 + MaxSpeed + true + true + ../../include + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions);NDEBUG + AssemblyAndSourceCode + $(IntDir) + false + false + Default + CompileAsCpp + true + + + true + true + + + + + + + + + + + Level4 + MaxSpeed + true + true + ../../include + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions);NDEBUG + AssemblyAndSourceCode + $(IntDir) + false + false + Default + CompileAsCpp + true + + + true + true + + + + + + + + + + + + + + + + + false + false + false + false + + + true + true + true + true + + + true + true + true + true + + + + + + + + + + true + true + true + true + + + + + + + + + + + + + + + + + + + diff --git a/extlib/mimalloc/ide/vs2017/mimalloc.vcxproj.filters b/extlib/mimalloc/ide/vs2017/mimalloc.vcxproj.filters new file mode 100644 index 0000000..4366051 --- /dev/null +++ b/extlib/mimalloc/ide/vs2017/mimalloc.vcxproj.filters @@ -0,0 +1,83 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + diff --git a/extlib/mimalloc/ide/vs2019/mimalloc-override-test.vcxproj b/extlib/mimalloc/ide/vs2019/mimalloc-override-test.vcxproj new file mode 100644 index 0000000..7a9202f --- /dev/null +++ b/extlib/mimalloc/ide/vs2019/mimalloc-override-test.vcxproj @@ -0,0 +1,190 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {FEF7868F-750E-4C21-A04D-22707CC66879} + mimalloc-override-test + 10.0 + mimalloc-override-test + + + + Application + true + v142 + + + Application + false + v142 + true + + + Application + true + v142 + + + Application + false + v142 + true + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + + Level3 + Disabled + true + true + ..\..\include + MultiThreadedDebugDLL + Sync + Default + false + + + Console + kernel32.lib;%(AdditionalDependencies) + + + + + + + + + + Level3 + Disabled + true + true + ..\..\include + MultiThreadedDebugDLL + Sync + Default + false + + + Console + + + kernel32.lib;%(AdditionalDependencies) + + + + + + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + _MBCS;%(PreprocessorDefinitions);NDEBUG + MultiThreadedDLL + + + true + true + Console + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + _MBCS;%(PreprocessorDefinitions);NDEBUG + MultiThreadedDLL + + + true + true + Console + + + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + + + + {abb5eae7-b3e6-432e-b636-333449892ea7} + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2019/mimalloc-override.vcxproj b/extlib/mimalloc/ide/vs2019/mimalloc-override.vcxproj new file mode 100644 index 0000000..a0e79fb --- /dev/null +++ b/extlib/mimalloc/ide/vs2019/mimalloc-override.vcxproj @@ -0,0 +1,257 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {ABB5EAE7-B3E6-432E-B636-333449892EA7} + mimalloc-override + 10.0 + mimalloc-override + + + + DynamicLibrary + true + v142 + + + DynamicLibrary + false + v142 + + + DynamicLibrary + true + v142 + + + DynamicLibrary + false + v142 + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .dll + mimalloc-override + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .dll + mimalloc-override + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .dll + mimalloc-override + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .dll + mimalloc-override + + + + Level3 + Disabled + true + true + ../../include + MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions); + MultiThreadedDebugDLL + false + Default + + + $(ProjectDir)\..\..\bin\mimalloc-redirect32.lib;%(AdditionalDependencies) + + + + + Default + false + + + COPY /Y $(ProjectDir)..\..\bin\mimalloc-redirect32.dll $(OutputPath) + + + Copy mimalloc-redirect32.dll to the output directory + + + + + Level3 + Disabled + true + true + ../../include + MI_DEBUG=3;MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions); + MultiThreadedDebugDLL + false + Default + + + $(ProjectDir)\..\..\bin\mimalloc-redirect.lib;%(AdditionalDependencies) + + + + + Default + false + + + COPY /Y $(ProjectDir)..\..\bin\mimalloc-redirect.dll $(OutputPath) + + + copy mimalloc-redirect.dll to the output directory + + + + + Level3 + MaxSpeed + true + true + true + ../../include + MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);NDEBUG + AssemblyAndSourceCode + $(IntDir) + false + MultiThreadedDLL + Default + false + + + true + true + $(ProjectDir)\..\..\bin\mimalloc-redirect32.lib;%(AdditionalDependencies) + + + Default + false + + + COPY /Y $(ProjectDir)..\..\bin\mimalloc-redirect32.dll $(OutputPath) + + + Copy mimalloc-redirect32.dll to the output directory + + + + + Level3 + MaxSpeed + true + true + true + ../../include + MI_SHARED_LIB;MI_SHARED_LIB_EXPORT;MI_MALLOC_OVERRIDE;%(PreprocessorDefinitions);NDEBUG + AssemblyAndSourceCode + $(IntDir) + false + MultiThreadedDLL + Default + false + + + true + true + $(ProjectDir)\..\..\bin\mimalloc-redirect.lib;%(AdditionalDependencies) + + + Default + false + + + COPY /Y $(ProjectDir)..\..\bin\mimalloc-redirect.dll $(OutputPath) + + + copy mimalloc-redirect.dll to the output directory + + + + + + + + + + + + + false + false + false + false + + + true + true + true + true + + + + + + true + + + + + + + + true + true + true + true + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2019/mimalloc-override.vcxproj.filters b/extlib/mimalloc/ide/vs2019/mimalloc-override.vcxproj.filters new file mode 100644 index 0000000..8e36f50 --- /dev/null +++ b/extlib/mimalloc/ide/vs2019/mimalloc-override.vcxproj.filters @@ -0,0 +1,81 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + {f1fccf27-17b9-42dd-ba51-6070baff85c6} + + + {39cb7e38-69d0-43fb-8406-6a0f7cefc3b4} + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2019/mimalloc-test-api.vcxproj b/extlib/mimalloc/ide/vs2019/mimalloc-test-api.vcxproj new file mode 100644 index 0000000..812a9cb --- /dev/null +++ b/extlib/mimalloc/ide/vs2019/mimalloc-test-api.vcxproj @@ -0,0 +1,155 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {FFF7958F-750E-4C21-A04D-22707CC66878} + mimalloc-test-api + 10.0 + mimalloc-test-api + + + + Application + true + v142 + + + Application + false + v142 + true + + + Application + true + v142 + + + Application + false + v142 + true + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + + Level3 + Disabled + true + true + ..\..\include + + + Console + + + + + Level3 + Disabled + true + true + ..\..\include + + + Console + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + %(PreprocessorDefinitions);NDEBUG + + + true + true + Console + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + %(PreprocessorDefinitions);NDEBUG + + + true + true + Console + + + + + + + + + {abb5eae7-b3e6-432e-b636-333449892ea6} + + + + + + diff --git a/extlib/mimalloc/ide/vs2019/mimalloc-test-stress.vcxproj b/extlib/mimalloc/ide/vs2019/mimalloc-test-stress.vcxproj new file mode 100644 index 0000000..afbb666 --- /dev/null +++ b/extlib/mimalloc/ide/vs2019/mimalloc-test-stress.vcxproj @@ -0,0 +1,159 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {FEF7958F-750E-4C21-A04D-22707CC66878} + mimalloc-test-stress + 10.0 + mimalloc-test-stress + + + + Application + true + v142 + + + Application + false + v142 + true + + + Application + true + v142 + + + Application + false + v142 + true + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + + Level3 + Disabled + true + true + ..\..\include + + + Console + + + + + Level3 + Disabled + true + true + ..\..\include + + + Console + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + %(PreprocessorDefinitions);NDEBUG + + + true + true + Console + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + %(PreprocessorDefinitions);NDEBUG + + + true + true + Console + + + + + false + false + false + false + + + + + {abb5eae7-b3e6-432e-b636-333449892ea7} + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2019/mimalloc-test.vcxproj b/extlib/mimalloc/ide/vs2019/mimalloc-test.vcxproj new file mode 100644 index 0000000..13af6ab --- /dev/null +++ b/extlib/mimalloc/ide/vs2019/mimalloc-test.vcxproj @@ -0,0 +1,158 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {FEF7858F-750E-4C21-A04D-22707CC66878} + mimalloctest + 10.0 + mimalloc-test + + + + Application + true + v142 + + + Application + false + v142 + true + + + Application + true + v142 + + + Application + false + v142 + true + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + $(ProjectDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(ProjectDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + + + + Level3 + Disabled + true + true + ..\..\include + stdcpp17 + + + Console + + + + + Level3 + Disabled + true + true + ..\..\include + stdcpp17 + + + Console + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + _MBCS;%(PreprocessorDefinitions);NDEBUG + stdcpp17 + + + true + true + Console + + + + + Level3 + MaxSpeed + true + true + true + true + ..\..\include + _MBCS;%(PreprocessorDefinitions);NDEBUG + stdcpp17 + + + true + true + Console + + + + + {abb5eae7-b3e6-432e-b636-333449892ea6} + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2019/mimalloc.sln b/extlib/mimalloc/ide/vs2019/mimalloc.sln new file mode 100644 index 0000000..fcb938a --- /dev/null +++ b/extlib/mimalloc/ide/vs2019/mimalloc.sln @@ -0,0 +1,81 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29709.97 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mimalloc", "mimalloc.vcxproj", "{ABB5EAE7-B3E6-432E-B636-333449892EA6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mimalloc-test", "mimalloc-test.vcxproj", "{FEF7858F-750E-4C21-A04D-22707CC66878}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mimalloc-override", "mimalloc-override.vcxproj", "{ABB5EAE7-B3E6-432E-B636-333449892EA7}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mimalloc-override-test", "mimalloc-override-test.vcxproj", "{FEF7868F-750E-4C21-A04D-22707CC66879}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mimalloc-test-stress", "mimalloc-test-stress.vcxproj", "{FEF7958F-750E-4C21-A04D-22707CC66878}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mimalloc-test-api", "mimalloc-test-api.vcxproj", "{FFF7958F-750E-4C21-A04D-22707CC66878}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x64.ActiveCfg = Debug|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x64.Build.0 = Debug|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x86.ActiveCfg = Debug|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Debug|x86.Build.0 = Debug|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x64.ActiveCfg = Release|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x64.Build.0 = Release|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x86.ActiveCfg = Release|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA6}.Release|x86.Build.0 = Release|Win32 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x64.ActiveCfg = Debug|x64 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x64.Build.0 = Debug|x64 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x86.ActiveCfg = Debug|Win32 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Debug|x86.Build.0 = Debug|Win32 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x64.ActiveCfg = Release|x64 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x64.Build.0 = Release|x64 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x86.ActiveCfg = Release|Win32 + {FEF7858F-750E-4C21-A04D-22707CC66878}.Release|x86.Build.0 = Release|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x64.ActiveCfg = Debug|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x64.Build.0 = Debug|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x86.ActiveCfg = Debug|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Debug|x86.Build.0 = Debug|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x64.ActiveCfg = Release|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x64.Build.0 = Release|x64 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x86.ActiveCfg = Release|Win32 + {ABB5EAE7-B3E6-432E-B636-333449892EA7}.Release|x86.Build.0 = Release|Win32 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x64.ActiveCfg = Debug|x64 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x64.Build.0 = Debug|x64 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x86.ActiveCfg = Debug|Win32 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Debug|x86.Build.0 = Debug|Win32 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x64.ActiveCfg = Release|x64 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x64.Build.0 = Release|x64 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x86.ActiveCfg = Release|Win32 + {FEF7868F-750E-4C21-A04D-22707CC66879}.Release|x86.Build.0 = Release|Win32 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x64.ActiveCfg = Debug|x64 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x64.Build.0 = Debug|x64 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x86.ActiveCfg = Debug|Win32 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Debug|x86.Build.0 = Debug|Win32 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x64.ActiveCfg = Release|x64 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x64.Build.0 = Release|x64 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x86.ActiveCfg = Release|Win32 + {FEF7958F-750E-4C21-A04D-22707CC66878}.Release|x86.Build.0 = Release|Win32 + {FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|x64.ActiveCfg = Debug|x64 + {FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|x64.Build.0 = Debug|x64 + {FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|x86.ActiveCfg = Debug|Win32 + {FFF7958F-750E-4C21-A04D-22707CC66878}.Debug|x86.Build.0 = Debug|Win32 + {FFF7958F-750E-4C21-A04D-22707CC66878}.Release|x64.ActiveCfg = Release|x64 + {FFF7958F-750E-4C21-A04D-22707CC66878}.Release|x64.Build.0 = Release|x64 + {FFF7958F-750E-4C21-A04D-22707CC66878}.Release|x86.ActiveCfg = Release|Win32 + {FFF7958F-750E-4C21-A04D-22707CC66878}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4297F93D-486A-4243-995F-7D32F59AE82A} + EndGlobalSection +EndGlobal diff --git a/extlib/mimalloc/ide/vs2019/mimalloc.vcxproj b/extlib/mimalloc/ide/vs2019/mimalloc.vcxproj new file mode 100644 index 0000000..e18db0c --- /dev/null +++ b/extlib/mimalloc/ide/vs2019/mimalloc.vcxproj @@ -0,0 +1,253 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {ABB5EAE7-B3E6-432E-B636-333449892EA6} + mimalloc + 10.0 + mimalloc + + + + StaticLibrary + true + v142 + + + StaticLibrary + false + v142 + true + + + StaticLibrary + true + v142 + + + StaticLibrary + false + v142 + true + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .lib + mimalloc-static + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .lib + mimalloc-static + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .lib + mimalloc-static + + + $(SolutionDir)..\..\out\msvc-$(Platform)\$(Configuration)\ + $(SolutionDir)..\..\out\msvc-$(Platform)\$(ProjectName)\$(Configuration)\ + .lib + mimalloc-static + + + + Level3 + Disabled + true + true + ../../include + MI_DEBUG=3;%(PreprocessorDefinitions); + CompileAsCpp + false + Default + + + + + + + + + + + Level4 + Disabled + true + true + ../../include + MI_DEBUG=3;%(PreprocessorDefinitions); + CompileAsCpp + false + Default + + + + + + + + + + + + + + + + + + + Level3 + MaxSpeed + true + true + ../../include + %(PreprocessorDefinitions);NDEBUG + AssemblyAndSourceCode + $(IntDir) + false + false + Default + CompileAsCpp + true + Default + + + true + true + + + + + + + + + + + Level3 + MaxSpeed + true + true + ../../include + %(PreprocessorDefinitions);NDEBUG + AssemblyAndSourceCode + $(IntDir) + false + false + Default + CompileAsCpp + true + Default + + + true + true + + + + + + + + + + + + + + + + + false + false + false + false + + + true + true + true + true + + + true + true + true + true + + + + + + true + + + + + + + true + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extlib/mimalloc/ide/vs2019/mimalloc.vcxproj.filters b/extlib/mimalloc/ide/vs2019/mimalloc.vcxproj.filters new file mode 100644 index 0000000..4704fb2 --- /dev/null +++ b/extlib/mimalloc/ide/vs2019/mimalloc.vcxproj.filters @@ -0,0 +1,84 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + {2b556b10-f559-4b2d-896e-142652adbf0c} + + + {852a14ae-6dde-4e95-8077-ca705e97e5af} + + + diff --git a/extlib/mimalloc/include/mimalloc-atomic.h b/extlib/mimalloc/include/mimalloc-atomic.h new file mode 100644 index 0000000..722b6ad --- /dev/null +++ b/extlib/mimalloc/include/mimalloc-atomic.h @@ -0,0 +1,294 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_ATOMIC_H +#define MIMALLOC_ATOMIC_H + +// ------------------------------------------------------ +// Atomics +// We need to be portable between C, C++, and MSVC. +// ------------------------------------------------------ + +#if defined(_MSC_VER) +#define _Atomic(tp) tp +#define ATOMIC_VAR_INIT(x) x +#elif defined(__cplusplus) +#include +#define _Atomic(tp) std::atomic +#else +#include +#endif + +// ------------------------------------------------------ +// Atomic operations specialized for mimalloc +// ------------------------------------------------------ + +// Atomically add a value; returns the previous value. Memory ordering is relaxed. +static inline uintptr_t mi_atomic_add(volatile _Atomic(uintptr_t)* p, uintptr_t add); + +// Atomically "and" a value; returns the previous value. Memory ordering is relaxed. +static inline uintptr_t mi_atomic_and(volatile _Atomic(uintptr_t)* p, uintptr_t x); + +// Atomically "or" a value; returns the previous value. Memory ordering is relaxed. +static inline uintptr_t mi_atomic_or(volatile _Atomic(uintptr_t)* p, uintptr_t x); + +// Atomically compare and exchange a value; returns `true` if successful. +// May fail spuriously. Memory ordering as release on success, and relaxed on failure. +// (Note: expected and desired are in opposite order from atomic_compare_exchange) +static inline bool mi_atomic_cas_weak(volatile _Atomic(uintptr_t)* p, uintptr_t desired, uintptr_t expected); + +// Atomically compare and exchange a value; returns `true` if successful. +// Memory ordering is acquire-release +// (Note: expected and desired are in opposite order from atomic_compare_exchange) +static inline bool mi_atomic_cas_strong(volatile _Atomic(uintptr_t)* p, uintptr_t desired, uintptr_t expected); + +// Atomically exchange a value. Memory ordering is acquire-release. +static inline uintptr_t mi_atomic_exchange(volatile _Atomic(uintptr_t)* p, uintptr_t exchange); + +// Atomically read a value. Memory ordering is relaxed. +static inline uintptr_t mi_atomic_read_relaxed(const volatile _Atomic(uintptr_t)* p); + +// Atomically read a value. Memory ordering is acquire. +static inline uintptr_t mi_atomic_read(const volatile _Atomic(uintptr_t)* p); + +// Atomically write a value. Memory ordering is release. +static inline void mi_atomic_write(volatile _Atomic(uintptr_t)* p, uintptr_t x); + +// Yield +static inline void mi_atomic_yield(void); + +// Atomically add a 64-bit value; returns the previous value. +// Note: not using _Atomic(int64_t) as it is only used for statistics. +static inline void mi_atomic_addi64(volatile int64_t* p, int64_t add); + +// Atomically update `*p` with the maximum of `*p` and `x` as a 64-bit value. +// Returns the previous value. Note: not using _Atomic(int64_t) as it is only used for statistics. +static inline void mi_atomic_maxi64(volatile int64_t* p, int64_t x); + +// Atomically read a 64-bit value +// Note: not using _Atomic(int64_t) as it is only used for statistics. +static inline int64_t mi_atomic_readi64(volatile int64_t* p); + +// Atomically subtract a value; returns the previous value. +static inline uintptr_t mi_atomic_sub(volatile _Atomic(uintptr_t)* p, uintptr_t sub) { + return mi_atomic_add(p, (uintptr_t)(-((intptr_t)sub))); +} + +// Atomically increment a value; returns the incremented result. +static inline uintptr_t mi_atomic_increment(volatile _Atomic(uintptr_t)* p) { + return mi_atomic_add(p, 1); +} + +// Atomically decrement a value; returns the decremented result. +static inline uintptr_t mi_atomic_decrement(volatile _Atomic(uintptr_t)* p) { + return mi_atomic_sub(p, 1); +} + +// Atomically add a signed value; returns the previous value. +static inline intptr_t mi_atomic_addi(volatile _Atomic(intptr_t)* p, intptr_t add) { + return (intptr_t)mi_atomic_add((volatile _Atomic(uintptr_t)*)p, (uintptr_t)add); +} + +// Atomically subtract a signed value; returns the previous value. +static inline intptr_t mi_atomic_subi(volatile _Atomic(intptr_t)* p, intptr_t sub) { + return (intptr_t)mi_atomic_addi(p,-sub); +} + +// Atomically read a pointer; Memory order is relaxed (i.e. no fence, only atomic). +#define mi_atomic_read_ptr_relaxed(T,p) \ + (T*)(mi_atomic_read_relaxed((const volatile _Atomic(uintptr_t)*)(p))) + +// Atomically read a pointer; Memory order is acquire. +#define mi_atomic_read_ptr(T,p) \ + (T*)(mi_atomic_read((const volatile _Atomic(uintptr_t)*)(p))) + +// Atomically write a pointer; Memory order is acquire. +#define mi_atomic_write_ptr(T,p,x) \ + mi_atomic_write((volatile _Atomic(uintptr_t)*)(p), (uintptr_t)((T*)x)) + +// Atomically compare and exchange a pointer; returns `true` if successful. May fail spuriously. +// Memory order is release. (like a write) +// (Note: expected and desired are in opposite order from atomic_compare_exchange) +#define mi_atomic_cas_ptr_weak(T,p,desired,expected) \ + mi_atomic_cas_weak((volatile _Atomic(uintptr_t)*)(p), (uintptr_t)((T*)(desired)), (uintptr_t)((T*)(expected))) + +// Atomically compare and exchange a pointer; returns `true` if successful. Memory order is acquire_release. +// (Note: expected and desired are in opposite order from atomic_compare_exchange) +#define mi_atomic_cas_ptr_strong(T,p,desired,expected) \ + mi_atomic_cas_strong((volatile _Atomic(uintptr_t)*)(p),(uintptr_t)((T*)(desired)), (uintptr_t)((T*)(expected))) + +// Atomically exchange a pointer value. +#define mi_atomic_exchange_ptr(T,p,exchange) \ + (T*)mi_atomic_exchange((volatile _Atomic(uintptr_t)*)(p), (uintptr_t)((T*)exchange)) + + +#ifdef _MSC_VER +#define WIN32_LEAN_AND_MEAN +#include +#include +#ifdef _WIN64 +typedef LONG64 msc_intptr_t; +#define MI_64(f) f##64 +#else +typedef LONG msc_intptr_t; +#define MI_64(f) f +#endif +static inline uintptr_t mi_atomic_add(volatile _Atomic(uintptr_t)* p, uintptr_t add) { + return (uintptr_t)MI_64(_InterlockedExchangeAdd)((volatile msc_intptr_t*)p, (msc_intptr_t)add); +} +static inline uintptr_t mi_atomic_and(volatile _Atomic(uintptr_t)* p, uintptr_t x) { + return (uintptr_t)MI_64(_InterlockedAnd)((volatile msc_intptr_t*)p, (msc_intptr_t)x); +} +static inline uintptr_t mi_atomic_or(volatile _Atomic(uintptr_t)* p, uintptr_t x) { + return (uintptr_t)MI_64(_InterlockedOr)((volatile msc_intptr_t*)p, (msc_intptr_t)x); +} +static inline bool mi_atomic_cas_strong(volatile _Atomic(uintptr_t)* p, uintptr_t desired, uintptr_t expected) { + return (expected == (uintptr_t)MI_64(_InterlockedCompareExchange)((volatile msc_intptr_t*)p, (msc_intptr_t)desired, (msc_intptr_t)expected)); +} +static inline bool mi_atomic_cas_weak(volatile _Atomic(uintptr_t)* p, uintptr_t desired, uintptr_t expected) { + return mi_atomic_cas_strong(p,desired,expected); +} +static inline uintptr_t mi_atomic_exchange(volatile _Atomic(uintptr_t)* p, uintptr_t exchange) { + return (uintptr_t)MI_64(_InterlockedExchange)((volatile msc_intptr_t*)p, (msc_intptr_t)exchange); +} +static inline uintptr_t mi_atomic_read(volatile _Atomic(uintptr_t) const* p) { + return *p; +} +static inline uintptr_t mi_atomic_read_relaxed(volatile _Atomic(uintptr_t) const* p) { + return *p; +} +static inline void mi_atomic_write(volatile _Atomic(uintptr_t)* p, uintptr_t x) { + #if defined(_M_IX86) || defined(_M_X64) + *p = x; + #else + mi_atomic_exchange(p,x); + #endif +} +static inline void mi_atomic_yield(void) { + YieldProcessor(); +} +static inline void mi_atomic_addi64(volatile _Atomic(int64_t)* p, int64_t add) { + #ifdef _WIN64 + mi_atomic_addi(p,add); + #else + int64_t current; + int64_t sum; + do { + current = *p; + sum = current + add; + } while (_InterlockedCompareExchange64(p, sum, current) != current); + #endif +} + +static inline void mi_atomic_maxi64(volatile _Atomic(int64_t)*p, int64_t x) { + int64_t current; + do { + current = *p; + } while (current < x && _InterlockedCompareExchange64(p, x, current) != current); +} + +static inline int64_t mi_atomic_readi64(volatile _Atomic(int64_t)*p) { + #ifdef _WIN64 + return *p; + #else + int64_t current; + do { + current = *p; + } while (_InterlockedCompareExchange64(p, current, current) != current); + return current; + #endif +} + +#else +#ifdef __cplusplus +#define MI_USING_STD using namespace std; +#else +#define MI_USING_STD +#endif +static inline uintptr_t mi_atomic_add(volatile _Atomic(uintptr_t)* p, uintptr_t add) { + MI_USING_STD + return atomic_fetch_add_explicit(p, add, memory_order_relaxed); +} +static inline uintptr_t mi_atomic_and(volatile _Atomic(uintptr_t)* p, uintptr_t x) { + MI_USING_STD + return atomic_fetch_and_explicit(p, x, memory_order_acq_rel); +} +static inline uintptr_t mi_atomic_or(volatile _Atomic(uintptr_t)* p, uintptr_t x) { + MI_USING_STD + return atomic_fetch_or_explicit(p, x, memory_order_acq_rel); +} +static inline bool mi_atomic_cas_weak(volatile _Atomic(uintptr_t)* p, uintptr_t desired, uintptr_t expected) { + MI_USING_STD + return atomic_compare_exchange_weak_explicit(p, &expected, desired, memory_order_acq_rel, memory_order_acquire); +} +static inline bool mi_atomic_cas_strong(volatile _Atomic(uintptr_t)* p, uintptr_t desired, uintptr_t expected) { + MI_USING_STD + return atomic_compare_exchange_strong_explicit(p, &expected, desired, memory_order_acq_rel, memory_order_acquire); +} +static inline uintptr_t mi_atomic_exchange(volatile _Atomic(uintptr_t)* p, uintptr_t exchange) { + MI_USING_STD + return atomic_exchange_explicit(p, exchange, memory_order_acq_rel); +} +static inline uintptr_t mi_atomic_read_relaxed(const volatile _Atomic(uintptr_t)* p) { + MI_USING_STD + return atomic_load_explicit((volatile _Atomic(uintptr_t)*) p, memory_order_relaxed); +} +static inline uintptr_t mi_atomic_read(const volatile _Atomic(uintptr_t)* p) { + MI_USING_STD + return atomic_load_explicit((volatile _Atomic(uintptr_t)*) p, memory_order_acquire); +} +static inline void mi_atomic_write(volatile _Atomic(uintptr_t)* p, uintptr_t x) { + MI_USING_STD + return atomic_store_explicit(p, x, memory_order_release); +} +static inline void mi_atomic_addi64(volatile int64_t* p, int64_t add) { + MI_USING_STD + atomic_fetch_add_explicit((volatile _Atomic(int64_t)*)p, add, memory_order_relaxed); +} +static inline int64_t mi_atomic_readi64(volatile int64_t* p) { + MI_USING_STD + return atomic_load_explicit((volatile _Atomic(int64_t)*) p, memory_order_relaxed); +} +static inline void mi_atomic_maxi64(volatile int64_t* p, int64_t x) { + MI_USING_STD + int64_t current; + do { + current = mi_atomic_readi64(p); + } while (current < x && !atomic_compare_exchange_weak_explicit((volatile _Atomic(int64_t)*)p, ¤t, x, memory_order_acq_rel, memory_order_relaxed)); +} + +#if defined(__cplusplus) + #include + static inline void mi_atomic_yield(void) { + std::this_thread::yield(); + } +#elif (defined(__GNUC__) || defined(__clang__)) && \ + (defined(__x86_64__) || defined(__i386__) || defined(__arm__) || defined(__aarch64__)) +#if defined(__x86_64__) || defined(__i386__) + static inline void mi_atomic_yield(void) { + asm volatile ("pause" ::: "memory"); + } +#elif defined(__arm__) || defined(__aarch64__) + static inline void mi_atomic_yield(void) { + asm volatile("yield"); + } +#endif +#elif defined(__wasi__) + #include + static inline void mi_atomic_yield(void) { + sched_yield(); + } +#else + #include + static inline void mi_atomic_yield(void) { + sleep(0); + } +#endif + +#endif + +#endif // __MIMALLOC_ATOMIC_H diff --git a/extlib/mimalloc/include/mimalloc-internal.h b/extlib/mimalloc/include/mimalloc-internal.h new file mode 100644 index 0000000..2dc7e36 --- /dev/null +++ b/extlib/mimalloc/include/mimalloc-internal.h @@ -0,0 +1,748 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_INTERNAL_H +#define MIMALLOC_INTERNAL_H + +#include "mimalloc-types.h" + +#if (MI_DEBUG>0) +#define mi_trace_message(...) _mi_trace_message(__VA_ARGS__) +#else +#define mi_trace_message(...) +#endif + +#define MI_CACHE_LINE 64 +#if defined(_MSC_VER) +#pragma warning(disable:4127) // suppress constant conditional warning (due to MI_SECURE paths) +#define mi_decl_noinline __declspec(noinline) +#define mi_decl_thread __declspec(thread) +#define mi_decl_cache_align __declspec(align(MI_CACHE_LINE)) +#elif (defined(__GNUC__) && (__GNUC__>=3)) // includes clang and icc +#define mi_decl_noinline __attribute__((noinline)) +#define mi_decl_thread __thread +#define mi_decl_cache_align __attribute__((aligned(MI_CACHE_LINE))) +#else +#define mi_decl_noinline +#define mi_decl_thread __thread // hope for the best :-) +#define mi_decl_cache_align +#endif + + +// "options.c" +void _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* message); +void _mi_fprintf(mi_output_fun* out, void* arg, const char* fmt, ...); +void _mi_warning_message(const char* fmt, ...); +void _mi_verbose_message(const char* fmt, ...); +void _mi_trace_message(const char* fmt, ...); +void _mi_options_init(void); +void _mi_error_message(int err, const char* fmt, ...); + +// random.c +void _mi_random_init(mi_random_ctx_t* ctx); +void _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* new_ctx); +uintptr_t _mi_random_next(mi_random_ctx_t* ctx); +uintptr_t _mi_heap_random_next(mi_heap_t* heap); +uintptr_t _os_random_weak(uintptr_t extra_seed); +static inline uintptr_t _mi_random_shuffle(uintptr_t x); + +// init.c +extern mi_stats_t _mi_stats_main; +extern const mi_page_t _mi_page_empty; +bool _mi_is_main_thread(void); +bool _mi_preloading(); // true while the C runtime is not ready + +// os.c +size_t _mi_os_page_size(void); +void _mi_os_init(void); // called from process init +void* _mi_os_alloc(size_t size, mi_stats_t* stats); // to allocate thread local data +void _mi_os_free(void* p, size_t size, mi_stats_t* stats); // to free thread local data +size_t _mi_os_good_alloc_size(size_t size); + +// memory.c +void* _mi_mem_alloc_aligned(size_t size, size_t alignment, bool* commit, bool* large, bool* is_zero, size_t* id, mi_os_tld_t* tld); +void _mi_mem_free(void* p, size_t size, size_t id, bool fully_committed, bool any_reset, mi_os_tld_t* tld); + +bool _mi_mem_reset(void* p, size_t size, mi_os_tld_t* tld); +bool _mi_mem_unreset(void* p, size_t size, bool* is_zero, mi_os_tld_t* tld); +bool _mi_mem_commit(void* p, size_t size, bool* is_zero, mi_os_tld_t* tld); +bool _mi_mem_protect(void* addr, size_t size); +bool _mi_mem_unprotect(void* addr, size_t size); + +void _mi_mem_collect(mi_os_tld_t* tld); + +// "segment.c" +mi_page_t* _mi_segment_page_alloc(mi_heap_t* heap, size_t block_wsize, mi_segments_tld_t* tld, mi_os_tld_t* os_tld); +void _mi_segment_page_free(mi_page_t* page, bool force, mi_segments_tld_t* tld); +void _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld); +uint8_t* _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t block_size, size_t* page_size, size_t* pre_size); // page start for any page +void _mi_segment_huge_page_free(mi_segment_t* segment, mi_page_t* page, mi_block_t* block); + +void _mi_segment_thread_collect(mi_segments_tld_t* tld); +void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld); +void _mi_abandoned_await_readers(void); + + + +// "page.c" +void* _mi_malloc_generic(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc; + +void _mi_page_retire(mi_page_t* page); // free the page if there are no other pages with many free blocks +void _mi_page_unfull(mi_page_t* page); +void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force); // free the page +void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq); // abandon the page, to be picked up by another thread... +void _mi_heap_delayed_free(mi_heap_t* heap); +void _mi_heap_collect_retired(mi_heap_t* heap, bool force); + +void _mi_page_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never); +size_t _mi_page_queue_append(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_queue_t* append); +void _mi_deferred_free(mi_heap_t* heap, bool force); + +void _mi_page_free_collect(mi_page_t* page,bool force); +void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page); // callback from segments + +size_t _mi_bin_size(uint8_t bin); // for stats +uint8_t _mi_bin(size_t size); // for stats +uint8_t _mi_bsr(uintptr_t x); // bit-scan-right, used on BSD in "os.c" + +// "heap.c" +void _mi_heap_destroy_pages(mi_heap_t* heap); +void _mi_heap_collect_abandon(mi_heap_t* heap); +void _mi_heap_set_default_direct(mi_heap_t* heap); + +// "stats.c" +void _mi_stats_done(mi_stats_t* stats); + +mi_msecs_t _mi_clock_now(void); +mi_msecs_t _mi_clock_end(mi_msecs_t start); +mi_msecs_t _mi_clock_start(void); + +// "alloc.c" +void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept; // called from `_mi_malloc_generic` +void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero); +void* _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero); +mi_block_t* _mi_page_ptr_unalign(const mi_segment_t* segment, const mi_page_t* page, const void* p); +bool _mi_free_delayed_block(mi_block_t* block); +void _mi_block_zero_init(const mi_page_t* page, void* p, size_t size); + +#if MI_DEBUG>1 +bool _mi_page_is_valid(mi_page_t* page); +#endif + + +// ------------------------------------------------------ +// Branches +// ------------------------------------------------------ + +#if defined(__GNUC__) || defined(__clang__) +#define mi_unlikely(x) __builtin_expect((x),0) +#define mi_likely(x) __builtin_expect((x),1) +#else +#define mi_unlikely(x) (x) +#define mi_likely(x) (x) +#endif + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + + +/* ----------------------------------------------------------- + Error codes passed to `_mi_fatal_error` + All are recoverable but EFAULT is a serious error and aborts by default in secure mode. + For portability define undefined error codes using common Unix codes: + +----------------------------------------------------------- */ +#include +#ifndef EAGAIN // double free +#define EAGAIN (11) +#endif +#ifndef ENOMEM // out of memory +#define ENOMEM (12) +#endif +#ifndef EFAULT // corrupted free-list or meta-data +#define EFAULT (14) +#endif +#ifndef EINVAL // trying to free an invalid pointer +#define EINVAL (22) +#endif +#ifndef EOVERFLOW // count*size overflow +#define EOVERFLOW (75) +#endif + + +/* ----------------------------------------------------------- + Inlined definitions +----------------------------------------------------------- */ +#define UNUSED(x) (void)(x) +#if (MI_DEBUG>0) +#define UNUSED_RELEASE(x) +#else +#define UNUSED_RELEASE(x) UNUSED(x) +#endif + +#define MI_INIT4(x) x(),x(),x(),x() +#define MI_INIT8(x) MI_INIT4(x),MI_INIT4(x) +#define MI_INIT16(x) MI_INIT8(x),MI_INIT8(x) +#define MI_INIT32(x) MI_INIT16(x),MI_INIT16(x) +#define MI_INIT64(x) MI_INIT32(x),MI_INIT32(x) +#define MI_INIT128(x) MI_INIT64(x),MI_INIT64(x) +#define MI_INIT256(x) MI_INIT128(x),MI_INIT128(x) + + +// Is `x` a power of two? (0 is considered a power of two) +static inline bool _mi_is_power_of_two(uintptr_t x) { + return ((x & (x - 1)) == 0); +} + +// Align upwards +static inline uintptr_t _mi_align_up(uintptr_t sz, size_t alignment) { + mi_assert_internal(alignment != 0); + uintptr_t mask = alignment - 1; + if ((alignment & mask) == 0) { // power of two? + return ((sz + mask) & ~mask); + } + else { + return (((sz + mask)/alignment)*alignment); + } +} + +// Divide upwards: `s <= _mi_divide_up(s,d)*d < s+d`. +static inline uintptr_t _mi_divide_up(uintptr_t size, size_t divider) { + mi_assert_internal(divider != 0); + return (divider == 0 ? size : ((size + divider - 1) / divider)); +} + +// Is memory zero initialized? +static inline bool mi_mem_is_zero(void* p, size_t size) { + for (size_t i = 0; i < size; i++) { + if (((uint8_t*)p)[i] != 0) return false; + } + return true; +} + +// Align a byte size to a size in _machine words_, +// i.e. byte size == `wsize*sizeof(void*)`. +static inline size_t _mi_wsize_from_size(size_t size) { + mi_assert_internal(size <= SIZE_MAX - sizeof(uintptr_t)); + return (size + sizeof(uintptr_t) - 1) / sizeof(uintptr_t); +} + +// Does malloc satisfy the alignment constraints already? +static inline bool mi_malloc_satisfies_alignment(size_t alignment, size_t size) { + return (alignment == sizeof(void*) || (alignment == MI_MAX_ALIGN_SIZE && size > (MI_MAX_ALIGN_SIZE/2))); +} + +// Overflow detecting multiply +#if __has_builtin(__builtin_umul_overflow) || __GNUC__ >= 5 +#include // UINT_MAX, ULONG_MAX +#if defined(_CLOCK_T) // for Illumos +#undef _CLOCK_T +#endif +static inline bool mi_mul_overflow(size_t count, size_t size, size_t* total) { + #if (SIZE_MAX == UINT_MAX) + return __builtin_umul_overflow(count, size, total); + #elif (SIZE_MAX == ULONG_MAX) + return __builtin_umull_overflow(count, size, total); + #else + return __builtin_umulll_overflow(count, size, total); + #endif +} +#else /* __builtin_umul_overflow is unavailable */ +static inline bool mi_mul_overflow(size_t count, size_t size, size_t* total) { + #define MI_MUL_NO_OVERFLOW ((size_t)1 << (4*sizeof(size_t))) // sqrt(SIZE_MAX) + *total = count * size; + return ((size >= MI_MUL_NO_OVERFLOW || count >= MI_MUL_NO_OVERFLOW) + && size > 0 && (SIZE_MAX / size) < count); +} +#endif + +// Safe multiply `count*size` into `total`; return `true` on overflow. +static inline bool mi_count_size_overflow(size_t count, size_t size, size_t* total) { + if (count==1) { // quick check for the case where count is one (common for C++ allocators) + *total = size; + return false; + } + else if (mi_unlikely(mi_mul_overflow(count, size, total))) { + _mi_error_message(EOVERFLOW, "allocation request is too large (%zu * %zu bytes)\n", count, size); + *total = SIZE_MAX; + return true; + } + else return false; +} + + +/* ---------------------------------------------------------------------------------------- +The thread local default heap: `_mi_get_default_heap` returns the thread local heap. +On most platforms (Windows, Linux, FreeBSD, NetBSD, etc), this just returns a +__thread local variable (`_mi_heap_default`). With the initial-exec TLS model this ensures +that the storage will always be available (allocated on the thread stacks). +On some platforms though we cannot use that when overriding `malloc` since the underlying +TLS implementation (or the loader) will call itself `malloc` on a first access and recurse. +We try to circumvent this in an efficient way: +- macOSX : we use an unused TLS slot from the OS allocated slots (MI_TLS_SLOT). On OSX, the + loader itself calls `malloc` even before the modules are initialized. +- OpenBSD: we use an unused slot from the pthread block (MI_TLS_PTHREAD_SLOT_OFS). +- DragonFly: not yet working. +------------------------------------------------------------------------------------------- */ + +extern const mi_heap_t _mi_heap_empty; // read-only empty heap, initial value of the thread local default heap +extern bool _mi_process_is_initialized; +mi_heap_t* _mi_heap_main_get(void); // statically allocated main backing heap + +#if defined(MI_MALLOC_OVERRIDE) +#if defined(__MACH__) // OSX +#define MI_TLS_SLOT 89 // seems unused? +// other possible unused ones are 9, 29, __PTK_FRAMEWORK_JAVASCRIPTCORE_KEY4 (94), __PTK_FRAMEWORK_GC_KEY9 (112) and __PTK_FRAMEWORK_OLDGC_KEY9 (89) +// see +#elif defined(__OpenBSD__) +// use end bytes of a name; goes wrong if anyone uses names > 23 characters (ptrhread specifies 16) +// see +#define MI_TLS_PTHREAD_SLOT_OFS (6*sizeof(int) + 4*sizeof(void*) + 24) +#elif defined(__DragonFly__) +#warning "mimalloc is not working correctly on DragonFly yet." +#define MI_TLS_PTHREAD_SLOT_OFS (4 + 1*sizeof(void*)) // offset `uniqueid` (also used by gdb?) +#endif +#endif + +#if defined(MI_TLS_SLOT) +static inline void* mi_tls_slot(size_t slot) mi_attr_noexcept; // forward declaration +#elif defined(MI_TLS_PTHREAD_SLOT_OFS) +#include +static inline mi_heap_t** mi_tls_pthread_heap_slot(void) { + pthread_t self = pthread_self(); + #if defined(__DragonFly__) + if (self==NULL) { + static mi_heap_t* pheap_main = _mi_heap_main_get(); + return &pheap_main; + } + #endif + return (mi_heap_t**)((uint8_t*)self + MI_TLS_PTHREAD_SLOT_OFS); +} +#elif defined(MI_TLS_PTHREAD) +#include +extern pthread_key_t _mi_heap_default_key; +#else +extern mi_decl_thread mi_heap_t* _mi_heap_default; // default heap to allocate from +#endif + +static inline mi_heap_t* mi_get_default_heap(void) { +#if defined(MI_TLS_SLOT) + mi_heap_t* heap = (mi_heap_t*)mi_tls_slot(MI_TLS_SLOT); + return (mi_unlikely(heap == NULL) ? (mi_heap_t*)&_mi_heap_empty : heap); +#elif defined(MI_TLS_PTHREAD_SLOT_OFS) + mi_heap_t* heap = *mi_tls_pthread_heap_slot(); + return (mi_unlikely(heap == NULL) ? (mi_heap_t*)&_mi_heap_empty : heap); +#elif defined(MI_TLS_PTHREAD) + mi_heap_t* heap = (mi_unlikely(_mi_heap_default_key == (pthread_key_t)(-1)) ? _mi_heap_main_get() : (mi_heap_t*)pthread_getspecific(_mi_heap_default_key)); + return (mi_unlikely(heap == NULL) ? (mi_heap_t*)&_mi_heap_empty : heap); +#else + #if defined(MI_TLS_RECURSE_GUARD) + if (mi_unlikely(!_mi_process_is_initialized)) return _mi_heap_main_get(); + #endif + return _mi_heap_default; +#endif +} + +static inline bool mi_heap_is_default(const mi_heap_t* heap) { + return (heap == mi_get_default_heap()); +} + +static inline bool mi_heap_is_backing(const mi_heap_t* heap) { + return (heap->tld->heap_backing == heap); +} + +static inline bool mi_heap_is_initialized(mi_heap_t* heap) { + mi_assert_internal(heap != NULL); + return (heap != &_mi_heap_empty); +} + +static inline uintptr_t _mi_ptr_cookie(const void* p) { + extern mi_heap_t _mi_heap_main; + mi_assert_internal(_mi_heap_main.cookie != 0); + return ((uintptr_t)p ^ _mi_heap_main.cookie); +} + +/* ----------------------------------------------------------- + Pages +----------------------------------------------------------- */ + +static inline mi_page_t* _mi_heap_get_free_small_page(mi_heap_t* heap, size_t size) { + mi_assert_internal(size <= (MI_SMALL_SIZE_MAX + MI_PADDING_SIZE)); + const size_t idx = _mi_wsize_from_size(size); + mi_assert_internal(idx < MI_PAGES_DIRECT); + return heap->pages_free_direct[idx]; +} + +// Get the page belonging to a certain size class +static inline mi_page_t* _mi_get_free_small_page(size_t size) { + return _mi_heap_get_free_small_page(mi_get_default_heap(), size); +} + +// Segment that contains the pointer +static inline mi_segment_t* _mi_ptr_segment(const void* p) { + // mi_assert_internal(p != NULL); + return (mi_segment_t*)((uintptr_t)p & ~MI_SEGMENT_MASK); +} + +// Segment belonging to a page +static inline mi_segment_t* _mi_page_segment(const mi_page_t* page) { + mi_segment_t* segment = _mi_ptr_segment(page); + mi_assert_internal(segment == NULL || page == &segment->pages[page->segment_idx]); + return segment; +} + +// used internally +static inline uintptr_t _mi_segment_page_idx_of(const mi_segment_t* segment, const void* p) { + // if (segment->page_size > MI_SEGMENT_SIZE) return &segment->pages[0]; // huge pages + ptrdiff_t diff = (uint8_t*)p - (uint8_t*)segment; + mi_assert_internal(diff >= 0 && (size_t)diff < MI_SEGMENT_SIZE); + uintptr_t idx = (uintptr_t)diff >> segment->page_shift; + mi_assert_internal(idx < segment->capacity); + mi_assert_internal(segment->page_kind <= MI_PAGE_MEDIUM || idx == 0); + return idx; +} + +// Get the page containing the pointer +static inline mi_page_t* _mi_segment_page_of(const mi_segment_t* segment, const void* p) { + uintptr_t idx = _mi_segment_page_idx_of(segment, p); + return &((mi_segment_t*)segment)->pages[idx]; +} + +// Quick page start for initialized pages +static inline uint8_t* _mi_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size) { + const size_t bsize = page->xblock_size; + mi_assert_internal(bsize > 0 && (bsize%sizeof(void*)) == 0); + return _mi_segment_page_start(segment, page, bsize, page_size, NULL); +} + +// Get the page containing the pointer +static inline mi_page_t* _mi_ptr_page(void* p) { + return _mi_segment_page_of(_mi_ptr_segment(p), p); +} + +// Get the block size of a page (special cased for huge objects) +static inline size_t mi_page_block_size(const mi_page_t* page) { + const size_t bsize = page->xblock_size; + mi_assert_internal(bsize > 0); + if (mi_likely(bsize < MI_HUGE_BLOCK_SIZE)) { + return bsize; + } + else { + size_t psize; + _mi_segment_page_start(_mi_page_segment(page), page, bsize, &psize, NULL); + return psize; + } +} + +// Get the usable block size of a page without fixed padding. +// This may still include internal padding due to alignment and rounding up size classes. +static inline size_t mi_page_usable_block_size(const mi_page_t* page) { + return mi_page_block_size(page) - MI_PADDING_SIZE; +} + + +// Thread free access +static inline mi_block_t* mi_page_thread_free(const mi_page_t* page) { + return (mi_block_t*)(mi_atomic_read_relaxed(&page->xthread_free) & ~3); +} + +static inline mi_delayed_t mi_page_thread_free_flag(const mi_page_t* page) { + return (mi_delayed_t)(mi_atomic_read_relaxed(&page->xthread_free) & 3); +} + +// Heap access +static inline mi_heap_t* mi_page_heap(const mi_page_t* page) { + return (mi_heap_t*)(mi_atomic_read_relaxed(&page->xheap)); +} + +static inline void mi_page_set_heap(mi_page_t* page, mi_heap_t* heap) { + mi_assert_internal(mi_page_thread_free_flag(page) != MI_DELAYED_FREEING); + mi_atomic_write(&page->xheap,(uintptr_t)heap); +} + +// Thread free flag helpers +static inline mi_block_t* mi_tf_block(mi_thread_free_t tf) { + return (mi_block_t*)(tf & ~0x03); +} +static inline mi_delayed_t mi_tf_delayed(mi_thread_free_t tf) { + return (mi_delayed_t)(tf & 0x03); +} +static inline mi_thread_free_t mi_tf_make(mi_block_t* block, mi_delayed_t delayed) { + return (mi_thread_free_t)((uintptr_t)block | (uintptr_t)delayed); +} +static inline mi_thread_free_t mi_tf_set_delayed(mi_thread_free_t tf, mi_delayed_t delayed) { + return mi_tf_make(mi_tf_block(tf),delayed); +} +static inline mi_thread_free_t mi_tf_set_block(mi_thread_free_t tf, mi_block_t* block) { + return mi_tf_make(block, mi_tf_delayed(tf)); +} + +// are all blocks in a page freed? +// note: needs up-to-date used count, (as the `xthread_free` list may not be empty). see `_mi_page_collect_free`. +static inline bool mi_page_all_free(const mi_page_t* page) { + mi_assert_internal(page != NULL); + return (page->used == 0); +} + +// are there any available blocks? +static inline bool mi_page_has_any_available(const mi_page_t* page) { + mi_assert_internal(page != NULL && page->reserved > 0); + return (page->used < page->reserved || (mi_page_thread_free(page) != NULL)); +} + +// are there immediately available blocks, i.e. blocks available on the free list. +static inline bool mi_page_immediate_available(const mi_page_t* page) { + mi_assert_internal(page != NULL); + return (page->free != NULL); +} + +// is more than 7/8th of a page in use? +static inline bool mi_page_mostly_used(const mi_page_t* page) { + if (page==NULL) return true; + uint16_t frac = page->reserved / 8U; + return (page->reserved - page->used <= frac); +} + +static inline mi_page_queue_t* mi_page_queue(const mi_heap_t* heap, size_t size) { + return &((mi_heap_t*)heap)->pages[_mi_bin(size)]; +} + + + +//----------------------------------------------------------- +// Page flags +//----------------------------------------------------------- +static inline bool mi_page_is_in_full(const mi_page_t* page) { + return page->flags.x.in_full; +} + +static inline void mi_page_set_in_full(mi_page_t* page, bool in_full) { + page->flags.x.in_full = in_full; +} + +static inline bool mi_page_has_aligned(const mi_page_t* page) { + return page->flags.x.has_aligned; +} + +static inline void mi_page_set_has_aligned(mi_page_t* page, bool has_aligned) { + page->flags.x.has_aligned = has_aligned; +} + + +/* ------------------------------------------------------------------- +Encoding/Decoding the free list next pointers + +This is to protect against buffer overflow exploits where the +free list is mutated. Many hardened allocators xor the next pointer `p` +with a secret key `k1`, as `p^k1`. This prevents overwriting with known +values but might be still too weak: if the attacker can guess +the pointer `p` this can reveal `k1` (since `p^k1^p == k1`). +Moreover, if multiple blocks can be read as well, the attacker can +xor both as `(p1^k1) ^ (p2^k1) == p1^p2` which may reveal a lot +about the pointers (and subsequently `k1`). + +Instead mimalloc uses an extra key `k2` and encodes as `((p^k2)<<> (MI_INTPTR_BITS - shift)))); +} +static inline uintptr_t mi_rotr(uintptr_t x, uintptr_t shift) { + shift %= MI_INTPTR_BITS; + return (shift==0 ? x : ((x >> shift) | (x << (MI_INTPTR_BITS - shift)))); +} + +static inline void* mi_ptr_decode(const void* null, const mi_encoded_t x, const uintptr_t* keys) { + void* p = (void*)(mi_rotr(x - keys[0], keys[0]) ^ keys[1]); + return (mi_unlikely(p==null) ? NULL : p); +} + +static inline mi_encoded_t mi_ptr_encode(const void* null, const void* p, const uintptr_t* keys) { + uintptr_t x = (uintptr_t)(mi_unlikely(p==NULL) ? null : p); + return mi_rotl(x ^ keys[1], keys[0]) + keys[0]; +} + +static inline mi_block_t* mi_block_nextx( const void* null, const mi_block_t* block, const uintptr_t* keys ) { + #ifdef MI_ENCODE_FREELIST + return (mi_block_t*)mi_ptr_decode(null, block->next, keys); + #else + UNUSED(keys); UNUSED(null); + return (mi_block_t*)block->next; + #endif +} + +static inline void mi_block_set_nextx(const void* null, mi_block_t* block, const mi_block_t* next, const uintptr_t* keys) { + #ifdef MI_ENCODE_FREELIST + block->next = mi_ptr_encode(null, next, keys); + #else + UNUSED(keys); UNUSED(null); + block->next = (mi_encoded_t)next; + #endif +} + +static inline mi_block_t* mi_block_next(const mi_page_t* page, const mi_block_t* block) { + #ifdef MI_ENCODE_FREELIST + mi_block_t* next = mi_block_nextx(page,block,page->keys); + // check for free list corruption: is `next` at least in the same page? + // TODO: check if `next` is `page->block_size` aligned? + if (mi_unlikely(next!=NULL && !mi_is_in_same_page(block, next))) { + _mi_error_message(EFAULT, "corrupted free list entry of size %zub at %p: value 0x%zx\n", mi_page_block_size(page), block, (uintptr_t)next); + next = NULL; + } + return next; + #else + UNUSED(page); + return mi_block_nextx(page,block,NULL); + #endif +} + +static inline void mi_block_set_next(const mi_page_t* page, mi_block_t* block, const mi_block_t* next) { + #ifdef MI_ENCODE_FREELIST + mi_block_set_nextx(page,block,next, page->keys); + #else + UNUSED(page); + mi_block_set_nextx(page,block,next,NULL); + #endif +} + +// ------------------------------------------------------------------- +// Fast "random" shuffle +// ------------------------------------------------------------------- + +static inline uintptr_t _mi_random_shuffle(uintptr_t x) { + if (x==0) { x = 17; } // ensure we don't get stuck in generating zeros +#if (MI_INTPTR_SIZE==8) + // by Sebastiano Vigna, see: + x ^= x >> 30; + x *= 0xbf58476d1ce4e5b9UL; + x ^= x >> 27; + x *= 0x94d049bb133111ebUL; + x ^= x >> 31; +#elif (MI_INTPTR_SIZE==4) + // by Chris Wellons, see: + x ^= x >> 16; + x *= 0x7feb352dUL; + x ^= x >> 15; + x *= 0x846ca68bUL; + x ^= x >> 16; +#endif + return x; +} + +// ------------------------------------------------------------------- +// Optimize numa node access for the common case (= one node) +// ------------------------------------------------------------------- + +int _mi_os_numa_node_get(mi_os_tld_t* tld); +size_t _mi_os_numa_node_count_get(void); + +extern size_t _mi_numa_node_count; +static inline int _mi_os_numa_node(mi_os_tld_t* tld) { + if (mi_likely(_mi_numa_node_count == 1)) return 0; + else return _mi_os_numa_node_get(tld); +} +static inline size_t _mi_os_numa_node_count(void) { + if (mi_likely(_mi_numa_node_count>0)) return _mi_numa_node_count; + else return _mi_os_numa_node_count_get(); +} + + +// ------------------------------------------------------------------- +// Getting the thread id should be performant as it is called in the +// fast path of `_mi_free` and we specialize for various platforms. +// ------------------------------------------------------------------- +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include +static inline uintptr_t _mi_thread_id(void) mi_attr_noexcept { + // Windows: works on Intel and ARM in both 32- and 64-bit + return (uintptr_t)NtCurrentTeb(); +} + +#elif defined(__GNUC__) && \ + (defined(__x86_64__) || defined(__i386__) || defined(__arm__) || defined(__aarch64__)) + +// TLS register on x86 is in the FS or GS register, see: https://akkadia.org/drepper/tls.pdf +static inline void* mi_tls_slot(size_t slot) mi_attr_noexcept { + void* res; + const size_t ofs = (slot*sizeof(void*)); +#if defined(__i386__) + __asm__("movl %%gs:%1, %0" : "=r" (res) : "m" (*((void**)ofs)) : ); // 32-bit always uses GS +#elif defined(__MACH__) && defined(__x86_64__) + __asm__("movq %%gs:%1, %0" : "=r" (res) : "m" (*((void**)ofs)) : ); // x86_64 macOSX uses GS +#elif defined(__x86_64__) + __asm__("movq %%fs:%1, %0" : "=r" (res) : "m" (*((void**)ofs)) : ); // x86_64 Linux, BSD uses FS +#elif defined(__arm__) + void** tcb; UNUSED(ofs); + asm volatile ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tcb)); + res = tcb[slot]; +#elif defined(__aarch64__) + void** tcb; UNUSED(ofs); + asm volatile ("mrs %0, tpidr_el0" : "=r" (tcb)); + res = tcb[slot]; +#endif + return res; +} + +// setting is only used on macOSX for now +static inline void mi_tls_slot_set(size_t slot, void* value) mi_attr_noexcept { + const size_t ofs = (slot*sizeof(void*)); +#if defined(__i386__) + __asm__("movl %1,%%gs:%0" : "=m" (*((void**)ofs)) : "rn" (value) : ); // 32-bit always uses GS +#elif defined(__MACH__) && defined(__x86_64__) + __asm__("movq %1,%%gs:%0" : "=m" (*((void**)ofs)) : "rn" (value) : ); // x86_64 macOSX uses GS +#elif defined(__x86_64__) + __asm__("movq %1,%%fs:%1" : "=m" (*((void**)ofs)) : "rn" (value) : ); // x86_64 Linux, BSD uses FS +#elif defined(__arm__) + void** tcb; UNUSED(ofs); + asm volatile ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tcb)); + tcb[slot] = value; +#elif defined(__aarch64__) + void** tcb; UNUSED(ofs); + asm volatile ("mrs %0, tpidr_el0" : "=r" (tcb)); + tcb[slot] = value; +#endif +} + +static inline uintptr_t _mi_thread_id(void) mi_attr_noexcept { + // in all our targets, slot 0 is the pointer to the thread control block + return (uintptr_t)mi_tls_slot(0); +} +#else +// otherwise use standard C +static inline uintptr_t _mi_thread_id(void) mi_attr_noexcept { + return (uintptr_t)&_mi_heap_default; +} +#endif + + +#endif diff --git a/extlib/mimalloc/include/mimalloc-new-delete.h b/extlib/mimalloc/include/mimalloc-new-delete.h new file mode 100644 index 0000000..fded0c0 --- /dev/null +++ b/extlib/mimalloc/include/mimalloc-new-delete.h @@ -0,0 +1,52 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018,2019 Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_NEW_DELETE_H +#define MIMALLOC_NEW_DELETE_H + +// ---------------------------------------------------------------------------- +// This header provides convenient overrides for the new and +// delete operations in C++. +// +// This header should be included in only one source file! +// +// On Windows, or when linking dynamically with mimalloc, these +// can be more performant than the standard new-delete operations. +// See +// --------------------------------------------------------------------------- +#if defined(__cplusplus) + #include + #include + + void operator delete(void* p) noexcept { mi_free(p); }; + void operator delete[](void* p) noexcept { mi_free(p); }; + + void* operator new(std::size_t n) noexcept(false) { return mi_new(n); } + void* operator new[](std::size_t n) noexcept(false) { return mi_new(n); } + + void* operator new (std::size_t n, const std::nothrow_t& tag) noexcept { (void)(tag); return mi_new_nothrow(n); } + void* operator new[](std::size_t n, const std::nothrow_t& tag) noexcept { (void)(tag); return mi_new_nothrow(n); } + + #if (__cplusplus >= 201402L || _MSC_VER >= 1916) + void operator delete (void* p, std::size_t n) noexcept { mi_free_size(p,n); }; + void operator delete[](void* p, std::size_t n) noexcept { mi_free_size(p,n); }; + #endif + + #if (__cplusplus > 201402L || defined(__cpp_aligned_new)) + void operator delete (void* p, std::align_val_t al) noexcept { mi_free_aligned(p, static_cast(al)); } + void operator delete[](void* p, std::align_val_t al) noexcept { mi_free_aligned(p, static_cast(al)); } + void operator delete (void* p, std::size_t n, std::align_val_t al) noexcept { mi_free_size_aligned(p, n, static_cast(al)); }; + void operator delete[](void* p, std::size_t n, std::align_val_t al) noexcept { mi_free_size_aligned(p, n, static_cast(al)); }; + + void* operator new( std::size_t n, std::align_val_t al) noexcept(false) { return mi_new_aligned(n, static_cast(al)); } + void* operator new[]( std::size_t n, std::align_val_t al) noexcept(false) { return mi_new_aligned(n, static_cast(al)); } + void* operator new (std::size_t n, std::align_val_t al, const std::nothrow_t&) noexcept { return mi_new_aligned_nothrow(n, static_cast(al)); } + void* operator new[](std::size_t n, std::align_val_t al, const std::nothrow_t&) noexcept { return mi_new_aligned_nothrow(n, static_cast(al)); } + #endif +#endif + +#endif // MIMALLOC_NEW_DELETE_H diff --git a/extlib/mimalloc/include/mimalloc-override.h b/extlib/mimalloc/include/mimalloc-override.h new file mode 100644 index 0000000..201fb8b --- /dev/null +++ b/extlib/mimalloc/include/mimalloc-override.h @@ -0,0 +1,66 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018,2019 Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_OVERRIDE_H +#define MIMALLOC_OVERRIDE_H + +/* ---------------------------------------------------------------------------- +This header can be used to statically redirect malloc/free and new/delete +to the mimalloc variants. This can be useful if one can include this file on +each source file in a project (but be careful when using external code to +not accidentally mix pointers from different allocators). +-----------------------------------------------------------------------------*/ + +#include + +// Standard C allocation +#define malloc(n) mi_malloc(n) +#define calloc(n,c) mi_calloc(n,c) +#define realloc(p,n) mi_realloc(p,n) +#define free(p) mi_free(p) + +#define strdup(s) mi_strdup(s) +#define strndup(s) mi_strndup(s) +#define realpath(f,n) mi_realpath(f,n) + +// Microsoft extensions +#define _expand(p,n) mi_expand(p,n) +#define _msize(p) mi_usable_size(p) +#define _recalloc(p,n,c) mi_recalloc(p,n,c) + +#define _strdup(s) mi_strdup(s) +#define _strndup(s) mi_strndup(s) +#define _wcsdup(s) (wchar_t*)mi_wcsdup((const unsigned short*)(s)) +#define _mbsdup(s) mi_mbsdup(s) +#define _dupenv_s(b,n,v) mi_dupenv_s(b,n,v) +#define _wdupenv_s(b,n,v) mi_wdupenv_s((unsigned short*)(b),n,(const unsigned short*)(v)) + +// Various Posix and Unix variants +#define reallocf(p,n) mi_reallocf(p,n) +#define malloc_size(p) mi_usable_size(p) +#define malloc_usable_size(p) mi_usable_size(p) +#define cfree(p) mi_free(p) + +#define valloc(n) mi_valloc(n) +#define pvalloc(n) mi_pvalloc(n) +#define reallocarray(p,s,n) mi_reallocarray(p,s,n) +#define memalign(a,n) mi_memalign(a,n) +#define aligned_alloc(a,n) mi_aligned_alloc(a,n) +#define posix_memalign(p,a,n) mi_posix_memalign(p,a,n) +#define _posix_memalign(p,a,n) mi_posix_memalign(p,a,n) + +// Microsoft aligned variants +#define _aligned_malloc(n,a) mi_malloc_aligned(n,a) +#define _aligned_realloc(p,n,a) mi_realloc_aligned(p,n,a) +#define _aligned_recalloc(p,s,n,a) mi_aligned_recalloc(p,s,n,a) +#define _aligned_msize(p,a,o) mi_usable_size(p) +#define _aligned_free(p) mi_free(p) +#define _aligned_offset_malloc(n,a,o) mi_malloc_aligned_at(n,a,o) +#define _aligned_offset_realloc(p,n,a,o) mi_realloc_aligned_at(p,n,a,o) +#define _aligned_offset_recalloc(p,s,n,a,o) mi_recalloc_aligned_at(p,s,n,a,o) + +#endif // MIMALLOC_OVERRIDE_H diff --git a/extlib/mimalloc/include/mimalloc-types.h b/extlib/mimalloc/include/mimalloc-types.h new file mode 100644 index 0000000..449e2e4 --- /dev/null +++ b/extlib/mimalloc/include/mimalloc-types.h @@ -0,0 +1,480 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_TYPES_H +#define MIMALLOC_TYPES_H + +#include // ptrdiff_t +#include // uintptr_t, uint16_t, etc +#include // _Atomic + +// Minimal alignment necessary. On most platforms 16 bytes are needed +// due to SSE registers for example. This must be at least `MI_INTPTR_SIZE` +#define MI_MAX_ALIGN_SIZE 16 // sizeof(max_align_t) + +// ------------------------------------------------------ +// Variants +// ------------------------------------------------------ + +// Define NDEBUG in the release version to disable assertions. +// #define NDEBUG + +// Define MI_STAT as 1 to maintain statistics; set it to 2 to have detailed statistics (but costs some performance). +// #define MI_STAT 1 + +// Define MI_SECURE to enable security mitigations +// #define MI_SECURE 1 // guard page around metadata +// #define MI_SECURE 2 // guard page around each mimalloc page +// #define MI_SECURE 3 // encode free lists (detect corrupted free list (buffer overflow), and invalid pointer free) +// #define MI_SECURE 4 // checks for double free. (may be more expensive) + +#if !defined(MI_SECURE) +#define MI_SECURE 0 +#endif + +// Define MI_DEBUG for debug mode +// #define MI_DEBUG 1 // basic assertion checks and statistics, check double free, corrupted free list, and invalid pointer free. +// #define MI_DEBUG 2 // + internal assertion checks +// #define MI_DEBUG 3 // + extensive internal invariant checking (cmake -DMI_DEBUG_FULL=ON) +#if !defined(MI_DEBUG) +#if !defined(NDEBUG) || defined(_DEBUG) +#define MI_DEBUG 2 +#else +#define MI_DEBUG 0 +#endif +#endif + +// Reserve extra padding at the end of each block to be more resilient against heap block overflows. +// The padding can detect byte-precise buffer overflow on free. +#if !defined(MI_PADDING) && (MI_DEBUG>=1) +#define MI_PADDING 1 +#endif + + +// Encoded free lists allow detection of corrupted free lists +// and can detect buffer overflows, modify after free, and double `free`s. +#if (MI_SECURE>=3 || MI_DEBUG>=1 || MI_PADDING > 0) +#define MI_ENCODE_FREELIST 1 +#endif + +// ------------------------------------------------------ +// Platform specific values +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Size of a pointer. +// We assume that `sizeof(void*)==sizeof(intptr_t)` +// and it holds for all platforms we know of. +// +// However, the C standard only requires that: +// p == (void*)((intptr_t)p)) +// but we also need: +// i == (intptr_t)((void*)i) +// or otherwise one might define an intptr_t type that is larger than a pointer... +// ------------------------------------------------------ + +#if INTPTR_MAX == 9223372036854775807LL +# define MI_INTPTR_SHIFT (3) +#elif INTPTR_MAX == 2147483647LL +# define MI_INTPTR_SHIFT (2) +#else +#error platform must be 32 or 64 bits +#endif + +#define MI_INTPTR_SIZE (1<= 655360) +#error "define more bins" +#endif + +// Used as a special value to encode block sizes in 32 bits. +#define MI_HUGE_BLOCK_SIZE ((uint32_t)MI_HUGE_OBJ_SIZE_MAX) + +// The free lists use encoded next fields +// (Only actually encodes when MI_ENCODED_FREELIST is defined.) +typedef uintptr_t mi_encoded_t; + +// free lists contain blocks +typedef struct mi_block_s { + mi_encoded_t next; +} mi_block_t; + + +// The delayed flags are used for efficient multi-threaded free-ing +typedef enum mi_delayed_e { + MI_USE_DELAYED_FREE = 0, // push on the owning heap thread delayed list + MI_DELAYED_FREEING = 1, // temporary: another thread is accessing the owning heap + MI_NO_DELAYED_FREE = 2, // optimize: push on page local thread free queue if another block is already in the heap thread delayed free list + MI_NEVER_DELAYED_FREE = 3 // sticky, only resets on page reclaim +} mi_delayed_t; + + +// The `in_full` and `has_aligned` page flags are put in a union to efficiently +// test if both are false (`full_aligned == 0`) in the `mi_free` routine. +typedef union mi_page_flags_s { + uint8_t full_aligned; + struct { + uint8_t in_full : 1; + uint8_t has_aligned : 1; + } x; +} mi_page_flags_t; + +// Thread free list. +// We use the bottom 2 bits of the pointer for mi_delayed_t flags +typedef uintptr_t mi_thread_free_t; + +// A page contains blocks of one specific size (`block_size`). +// Each page has three list of free blocks: +// `free` for blocks that can be allocated, +// `local_free` for freed blocks that are not yet available to `mi_malloc` +// `thread_free` for freed blocks by other threads +// The `local_free` and `thread_free` lists are migrated to the `free` list +// when it is exhausted. The separate `local_free` list is necessary to +// implement a monotonic heartbeat. The `thread_free` list is needed for +// avoiding atomic operations in the common case. +// +// +// `used - |thread_free|` == actual blocks that are in use (alive) +// `used - |thread_free| + |free| + |local_free| == capacity` +// +// We don't count `freed` (as |free|) but use `used` to reduce +// the number of memory accesses in the `mi_page_all_free` function(s). +// +// Notes: +// - Access is optimized for `mi_free` and `mi_page_alloc` (in `alloc.c`) +// - Using `uint16_t` does not seem to slow things down +// - The size is 8 words on 64-bit which helps the page index calculations +// (and 10 words on 32-bit, and encoded free lists add 2 words. Sizes 10 +// and 12 are still good for address calculation) +// - To limit the structure size, the `xblock_size` is 32-bits only; for +// blocks > MI_HUGE_BLOCK_SIZE the size is determined from the segment page size +// - `thread_free` uses the bottom bits as a delayed-free flags to optimize +// concurrent frees where only the first concurrent free adds to the owning +// heap `thread_delayed_free` list (see `alloc.c:mi_free_block_mt`). +// The invariant is that no-delayed-free is only set if there is +// at least one block that will be added, or as already been added, to +// the owning heap `thread_delayed_free` list. This guarantees that pages +// will be freed correctly even if only other threads free blocks. +typedef struct mi_page_s { + // "owned" by the segment + uint8_t segment_idx; // index in the segment `pages` array, `page == &segment->pages[page->segment_idx]` + uint8_t segment_in_use:1; // `true` if the segment allocated this page + uint8_t is_reset:1; // `true` if the page memory was reset + uint8_t is_committed:1; // `true` if the page virtual memory is committed + uint8_t is_zero_init:1; // `true` if the page was zero initialized + + // layout like this to optimize access in `mi_malloc` and `mi_free` + uint16_t capacity; // number of blocks committed, must be the first field, see `segment.c:page_clear` + uint16_t reserved; // number of blocks reserved in memory + mi_page_flags_t flags; // `in_full` and `has_aligned` flags (8 bits) + uint8_t is_zero:1; // `true` if the blocks in the free list are zero initialized + uint8_t retire_expire:7; // expiration count for retired blocks + + mi_block_t* free; // list of available free blocks (`malloc` allocates from this list) + #ifdef MI_ENCODE_FREELIST + uintptr_t keys[2]; // two random keys to encode the free lists (see `_mi_block_next`) + #endif + uint32_t used; // number of blocks in use (including blocks in `local_free` and `thread_free`) + uint32_t xblock_size; // size available in each block (always `>0`) + + mi_block_t* local_free; // list of deferred free blocks by this thread (migrates to `free`) + volatile _Atomic(mi_thread_free_t) xthread_free; // list of deferred free blocks freed by other threads + volatile _Atomic(uintptr_t) xheap; + + struct mi_page_s* next; // next page owned by this thread with the same `block_size` + struct mi_page_s* prev; // previous page owned by this thread with the same `block_size` +} mi_page_t; + + + +typedef enum mi_page_kind_e { + MI_PAGE_SMALL, // small blocks go into 64kb pages inside a segment + MI_PAGE_MEDIUM, // medium blocks go into 512kb pages inside a segment + MI_PAGE_LARGE, // larger blocks go into a single page spanning a whole segment + MI_PAGE_HUGE // huge blocks (>512kb) are put into a single page in a segment of the exact size (but still 2mb aligned) +} mi_page_kind_t; + +// Segments are large allocated memory blocks (2mb on 64 bit) from +// the OS. Inside segments we allocated fixed size _pages_ that +// contain blocks. +typedef struct mi_segment_s { + // memory fields + size_t memid; // id for the os-level memory manager + bool mem_is_fixed; // `true` if we cannot decommit/reset/protect in this memory (i.e. when allocated using large OS pages) + bool mem_is_committed; // `true` if the whole segment is eagerly committed + + // segment fields + struct mi_segment_s* next; // must be the first segment field -- see `segment.c:segment_alloc` + struct mi_segment_s* prev; + struct mi_segment_s* abandoned_next; + size_t abandoned; // abandoned pages (i.e. the original owning thread stopped) (`abandoned <= used`) + size_t abandoned_visits; // count how often this segment is visited in the abandoned list (to force reclaim it it is too long) + + size_t used; // count of pages in use (`used <= capacity`) + size_t capacity; // count of available pages (`#free + used`) + size_t segment_size;// for huge pages this may be different from `MI_SEGMENT_SIZE` + size_t segment_info_size; // space we are using from the first page for segment meta-data and possible guard pages. + uintptr_t cookie; // verify addresses in secure mode: `_mi_ptr_cookie(segment) == segment->cookie` + + // layout like this to optimize access in `mi_free` + size_t page_shift; // `1 << page_shift` == the page sizes == `page->block_size * page->reserved` (unless the first page, then `-segment_info_size`). + volatile _Atomic(uintptr_t) thread_id; // unique id of the thread owning this segment + mi_page_kind_t page_kind; // kind of pages: small, large, or huge + mi_page_t pages[1]; // up to `MI_SMALL_PAGES_PER_SEGMENT` pages +} mi_segment_t; + + +// ------------------------------------------------------ +// Heaps +// Provide first-class heaps to allocate from. +// A heap just owns a set of pages for allocation and +// can only be allocate/reallocate from the thread that created it. +// Freeing blocks can be done from any thread though. +// Per thread, the segments are shared among its heaps. +// Per thread, there is always a default heap that is +// used for allocation; it is initialized to statically +// point to an empty heap to avoid initialization checks +// in the fast path. +// ------------------------------------------------------ + +// Thread local data +typedef struct mi_tld_s mi_tld_t; + +// Pages of a certain block size are held in a queue. +typedef struct mi_page_queue_s { + mi_page_t* first; + mi_page_t* last; + size_t block_size; +} mi_page_queue_t; + +#define MI_BIN_FULL (MI_BIN_HUGE+1) + +// Random context +typedef struct mi_random_cxt_s { + uint32_t input[16]; + uint32_t output[16]; + int output_available; +} mi_random_ctx_t; + + +// In debug mode there is a padding stucture at the end of the blocks to check for buffer overflows +#if (MI_PADDING) +typedef struct mi_padding_s { + uint32_t canary; // encoded block value to check validity of the padding (in case of overflow) + uint32_t delta; // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes) +} mi_padding_t; +#define MI_PADDING_SIZE (sizeof(mi_padding_t)) +#define MI_PADDING_WSIZE ((MI_PADDING_SIZE + MI_INTPTR_SIZE - 1) / MI_INTPTR_SIZE) +#else +#define MI_PADDING_SIZE 0 +#define MI_PADDING_WSIZE 0 +#endif + +#define MI_PAGES_DIRECT (MI_SMALL_WSIZE_MAX + MI_PADDING_WSIZE + 1) + + +// A heap owns a set of pages. +struct mi_heap_s { + mi_tld_t* tld; + mi_page_t* pages_free_direct[MI_PAGES_DIRECT]; // optimize: array where every entry points a page with possibly free blocks in the corresponding queue for that size. + mi_page_queue_t pages[MI_BIN_FULL + 1]; // queue of pages for each size class (or "bin") + volatile _Atomic(mi_block_t*) thread_delayed_free; + uintptr_t thread_id; // thread this heap belongs too + uintptr_t cookie; // random cookie to verify pointers (see `_mi_ptr_cookie`) + uintptr_t keys[2]; // two random keys used to encode the `thread_delayed_free` list + mi_random_ctx_t random; // random number context used for secure allocation + size_t page_count; // total number of pages in the `pages` queues. + size_t page_retired_min; // smallest retired index (retired pages are fully free, but still in the page queues) + size_t page_retired_max; // largest retired index into the `pages` array. + mi_heap_t* next; // list of heaps per thread + bool no_reclaim; // `true` if this heap should not reclaim abandoned pages +}; + + + +// ------------------------------------------------------ +// Debug +// ------------------------------------------------------ + +#define MI_DEBUG_UNINIT (0xD0) +#define MI_DEBUG_FREED (0xDF) +#define MI_DEBUG_PADDING (0xDE) + +#if (MI_DEBUG) +// use our own assertion to print without memory allocation +void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line, const char* func ); +#define mi_assert(expr) ((expr) ? (void)0 : _mi_assert_fail(#expr,__FILE__,__LINE__,__func__)) +#else +#define mi_assert(x) +#endif + +#if (MI_DEBUG>1) +#define mi_assert_internal mi_assert +#else +#define mi_assert_internal(x) +#endif + +#if (MI_DEBUG>2) +#define mi_assert_expensive mi_assert +#else +#define mi_assert_expensive(x) +#endif + +// ------------------------------------------------------ +// Statistics +// ------------------------------------------------------ + +#ifndef MI_STAT +#if (MI_DEBUG>0) +#define MI_STAT 2 +#else +#define MI_STAT 0 +#endif +#endif + +typedef struct mi_stat_count_s { + int64_t allocated; + int64_t freed; + int64_t peak; + int64_t current; +} mi_stat_count_t; + +typedef struct mi_stat_counter_s { + int64_t total; + int64_t count; +} mi_stat_counter_t; + +typedef struct mi_stats_s { + mi_stat_count_t segments; + mi_stat_count_t pages; + mi_stat_count_t reserved; + mi_stat_count_t committed; + mi_stat_count_t reset; + mi_stat_count_t page_committed; + mi_stat_count_t segments_abandoned; + mi_stat_count_t pages_abandoned; + mi_stat_count_t threads; + mi_stat_count_t huge; + mi_stat_count_t giant; + mi_stat_count_t malloc; + mi_stat_count_t segments_cache; + mi_stat_counter_t pages_extended; + mi_stat_counter_t mmap_calls; + mi_stat_counter_t commit_calls; + mi_stat_counter_t page_no_retire; + mi_stat_counter_t searches; + mi_stat_counter_t huge_count; + mi_stat_counter_t giant_count; +#if MI_STAT>1 + mi_stat_count_t normal[MI_BIN_HUGE+1]; +#endif +} mi_stats_t; + + +void _mi_stat_increase(mi_stat_count_t* stat, size_t amount); +void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount); +void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount); + +#if (MI_STAT) +#define mi_stat_increase(stat,amount) _mi_stat_increase( &(stat), amount) +#define mi_stat_decrease(stat,amount) _mi_stat_decrease( &(stat), amount) +#define mi_stat_counter_increase(stat,amount) _mi_stat_counter_increase( &(stat), amount) +#else +#define mi_stat_increase(stat,amount) (void)0 +#define mi_stat_decrease(stat,amount) (void)0 +#define mi_stat_counter_increase(stat,amount) (void)0 +#endif + +#define mi_heap_stat_increase(heap,stat,amount) mi_stat_increase( (heap)->tld->stats.stat, amount) +#define mi_heap_stat_decrease(heap,stat,amount) mi_stat_decrease( (heap)->tld->stats.stat, amount) + +// ------------------------------------------------------ +// Thread Local data +// ------------------------------------------------------ + +typedef int64_t mi_msecs_t; + +// Queue of segments +typedef struct mi_segment_queue_s { + mi_segment_t* first; + mi_segment_t* last; +} mi_segment_queue_t; + +// OS thread local data +typedef struct mi_os_tld_s { + size_t region_idx; // start point for next allocation + mi_stats_t* stats; // points to tld stats +} mi_os_tld_t; + +// Segments thread local data +typedef struct mi_segments_tld_s { + mi_segment_queue_t small_free; // queue of segments with free small pages + mi_segment_queue_t medium_free; // queue of segments with free medium pages + mi_page_queue_t pages_reset; // queue of freed pages that can be reset + size_t count; // current number of segments; + size_t peak_count; // peak number of segments + size_t current_size; // current size of all segments + size_t peak_size; // peak size of all segments + size_t cache_count; // number of segments in the cache + size_t cache_size; // total size of all segments in the cache + mi_segment_t* cache; // (small) cache of segments + mi_stats_t* stats; // points to tld stats + mi_os_tld_t* os; // points to os stats +} mi_segments_tld_t; + +// Thread local data +struct mi_tld_s { + unsigned long long heartbeat; // monotonic heartbeat count + bool recurse; // true if deferred was called; used to prevent infinite recursion. + mi_heap_t* heap_backing; // backing heap of this thread (cannot be deleted) + mi_heap_t* heaps; // list of heaps in this thread (so we can abandon all when the thread terminates) + mi_segments_tld_t segments; // segment tld + mi_os_tld_t os; // os tld + mi_stats_t stats; // statistics +}; + +#endif diff --git a/extlib/mimalloc/include/mimalloc.h b/extlib/mimalloc/include/mimalloc.h new file mode 100644 index 0000000..8dd318c --- /dev/null +++ b/extlib/mimalloc/include/mimalloc.h @@ -0,0 +1,426 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2020, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_H +#define MIMALLOC_H + +#define MI_MALLOC_VERSION 164 // major + 2 digits minor + +// ------------------------------------------------------ +// Compiler specific attributes +// ------------------------------------------------------ + +#ifdef __cplusplus + #if (__cplusplus >= 201103L) || (_MSC_VER > 1900) // C++11 + #define mi_attr_noexcept noexcept + #else + #define mi_attr_noexcept throw() + #endif +#else + #define mi_attr_noexcept +#endif + +#if (__cplusplus >= 201703) + #define mi_decl_nodiscard [[nodiscard]] +#elif (__GNUC__ >= 4) || defined(__clang__) // includes clang, icc, and clang-cl + #define mi_decl_nodiscard __attribute__((warn_unused_result)) +#elif (_MSC_VER >= 1700) + #define mi_decl_nodiscard _Check_return_ +#else + #define mi_decl_nodiscard +#endif + +#if defined(_MSC_VER) || defined(__MINGW32__) + #if !defined(MI_SHARED_LIB) + #define mi_decl_export + #elif defined(MI_SHARED_LIB_EXPORT) + #define mi_decl_export __declspec(dllexport) + #else + #define mi_decl_export __declspec(dllimport) + #endif + #if defined(__MINGW32__) + #define mi_decl_restrict + #define mi_attr_malloc __attribute__((malloc)) + #else + #if (_MSC_VER >= 1900) && !defined(__EDG__) + #define mi_decl_restrict __declspec(allocator) __declspec(restrict) + #else + #define mi_decl_restrict __declspec(restrict) + #endif + #define mi_attr_malloc + #endif + #define mi_cdecl __cdecl + #define mi_attr_alloc_size(s) + #define mi_attr_alloc_size2(s1,s2) + #define mi_attr_alloc_align(p) +#elif defined(__GNUC__) // includes clang and icc + #define mi_cdecl // leads to warnings... __attribute__((cdecl)) + #define mi_decl_export __attribute__((visibility("default"))) + #define mi_decl_restrict + #define mi_attr_malloc __attribute__((malloc)) + #if (defined(__clang_major__) && (__clang_major__ < 4)) || (__GNUC__ < 5) + #define mi_attr_alloc_size(s) + #define mi_attr_alloc_size2(s1,s2) + #define mi_attr_alloc_align(p) + #elif defined(__INTEL_COMPILER) + #define mi_attr_alloc_size(s) __attribute__((alloc_size(s))) + #define mi_attr_alloc_size2(s1,s2) __attribute__((alloc_size(s1,s2))) + #define mi_attr_alloc_align(p) + #else + #define mi_attr_alloc_size(s) __attribute__((alloc_size(s))) + #define mi_attr_alloc_size2(s1,s2) __attribute__((alloc_size(s1,s2))) + #define mi_attr_alloc_align(p) __attribute__((alloc_align(p))) + #endif +#else + #define mi_cdecl + #define mi_decl_export + #define mi_decl_restrict + #define mi_attr_malloc + #define mi_attr_alloc_size(s) + #define mi_attr_alloc_size2(s1,s2) + #define mi_attr_alloc_align(p) +#endif + +// ------------------------------------------------------ +// Includes +// ------------------------------------------------------ + +#include // size_t +#include // bool + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------------ +// Standard malloc interface +// ------------------------------------------------------ + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc(size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2); +mi_decl_nodiscard mi_decl_export void* mi_realloc(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); +mi_decl_export void* mi_expand(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); + +mi_decl_export void mi_free(void* p) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_strdup(const char* s) mi_attr_noexcept mi_attr_malloc; +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_strndup(const char* s, size_t n) mi_attr_noexcept mi_attr_malloc; +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept mi_attr_malloc; + +// ------------------------------------------------------ +// Extended functionality +// ------------------------------------------------------ +#define MI_SMALL_WSIZE_MAX (128) +#define MI_SMALL_SIZE_MAX (MI_SMALL_WSIZE_MAX*sizeof(void*)) + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_small(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_mallocn(size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2); +mi_decl_nodiscard mi_decl_export void* mi_reallocn(void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(2,3); +mi_decl_nodiscard mi_decl_export void* mi_reallocf(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); + +mi_decl_nodiscard mi_decl_export size_t mi_usable_size(const void* p) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export size_t mi_good_size(size_t size) mi_attr_noexcept; + + +// ------------------------------------------------------ +// Internals +// ------------------------------------------------------ + +typedef void (mi_cdecl mi_deferred_free_fun)(bool force, unsigned long long heartbeat, void* arg); +mi_decl_export void mi_register_deferred_free(mi_deferred_free_fun* deferred_free, void* arg) mi_attr_noexcept; + +typedef void (mi_cdecl mi_output_fun)(const char* msg, void* arg); +mi_decl_export void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept; + +typedef void (mi_cdecl mi_error_fun)(int err, void* arg); +mi_decl_export void mi_register_error(mi_error_fun* fun, void* arg); + +mi_decl_export void mi_collect(bool force) mi_attr_noexcept; +mi_decl_export int mi_version(void) mi_attr_noexcept; +mi_decl_export void mi_stats_reset(void) mi_attr_noexcept; +mi_decl_export void mi_stats_merge(void) mi_attr_noexcept; +mi_decl_export void mi_stats_print(void* out) mi_attr_noexcept; // backward compatibility: `out` is ignored and should be NULL +mi_decl_export void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept; + +mi_decl_export void mi_process_init(void) mi_attr_noexcept; +mi_decl_export void mi_thread_init(void) mi_attr_noexcept; +mi_decl_export void mi_thread_done(void) mi_attr_noexcept; +mi_decl_export void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept; + + +// ------------------------------------------------------------------------------------- +// Aligned allocation +// Note that `alignment` always follows `size` for consistency with unaligned +// allocation, but unfortunately this differs from `posix_memalign` and `aligned_alloc`. +// ------------------------------------------------------------------------------------- + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_aligned(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_aligned(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc_aligned(size_t count, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2) mi_attr_alloc_align(3); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2); +mi_decl_nodiscard mi_decl_export void* mi_realloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(2) mi_attr_alloc_align(3); +mi_decl_nodiscard mi_decl_export void* mi_realloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(2); + + +// ------------------------------------------------------------------------------------- +// Heaps: first-class, but can only allocate from the same thread that created it. +// ------------------------------------------------------------------------------------- + +struct mi_heap_s; +typedef struct mi_heap_s mi_heap_t; + +mi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_new(void); +mi_decl_export void mi_heap_delete(mi_heap_t* heap); +mi_decl_export void mi_heap_destroy(mi_heap_t* heap); +mi_decl_export mi_heap_t* mi_heap_set_default(mi_heap_t* heap); +mi_decl_export mi_heap_t* mi_heap_get_default(void); +mi_decl_export mi_heap_t* mi_heap_get_backing(void); +mi_decl_export void mi_heap_collect(mi_heap_t* heap, bool force) mi_attr_noexcept; + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); + +mi_decl_nodiscard mi_decl_export void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3); +mi_decl_nodiscard mi_decl_export void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(3,4);; +mi_decl_nodiscard mi_decl_export void* mi_heap_reallocf(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3); + +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_strdup(mi_heap_t* heap, const char* s) mi_attr_noexcept mi_attr_malloc; +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n) mi_attr_noexcept mi_attr_malloc; +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept mi_attr_malloc; + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc_aligned(mi_heap_t* heap, size_t count, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3) mi_attr_alloc_align(4); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc_aligned_at(mi_heap_t* heap, size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3); +mi_decl_nodiscard mi_decl_export void* mi_heap_realloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(3) mi_attr_alloc_align(4); +mi_decl_nodiscard mi_decl_export void* mi_heap_realloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(3); + + +// -------------------------------------------------------------------------------- +// Zero initialized re-allocation. +// Only valid on memory that was originally allocated with zero initialization too. +// e.g. `mi_calloc`, `mi_zalloc`, `mi_zalloc_aligned` etc. +// see +// -------------------------------------------------------------------------------- + +mi_decl_nodiscard mi_decl_export void* mi_rezalloc(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export void* mi_recalloc(void* p, size_t newcount, size_t size) mi_attr_noexcept mi_attr_alloc_size2(2,3); + +mi_decl_nodiscard mi_decl_export void* mi_rezalloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(2) mi_attr_alloc_align(3); +mi_decl_nodiscard mi_decl_export void* mi_rezalloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export void* mi_recalloc_aligned(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept mi_attr_alloc_size2(2,3) mi_attr_alloc_align(4); +mi_decl_nodiscard mi_decl_export void* mi_recalloc_aligned_at(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size2(2,3); + +mi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3); +mi_decl_nodiscard mi_decl_export void* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t newcount, size_t size) mi_attr_noexcept mi_attr_alloc_size2(3,4); + +mi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(3) mi_attr_alloc_align(4); +mi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(3); +mi_decl_nodiscard mi_decl_export void* mi_heap_recalloc_aligned(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept mi_attr_alloc_size2(3,4) mi_attr_alloc_align(5); +mi_decl_nodiscard mi_decl_export void* mi_heap_recalloc_aligned_at(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size2(3,4); + + +// ------------------------------------------------------ +// Analysis +// ------------------------------------------------------ + +mi_decl_export bool mi_heap_contains_block(mi_heap_t* heap, const void* p); +mi_decl_export bool mi_heap_check_owned(mi_heap_t* heap, const void* p); +mi_decl_export bool mi_check_owned(const void* p); + +// An area of heap space contains blocks of a single size. +typedef struct mi_heap_area_s { + void* blocks; // start of the area containing heap blocks + size_t reserved; // bytes reserved for this area (virtual) + size_t committed; // current available bytes for this area + size_t used; // bytes in use by allocated blocks + size_t block_size; // size in bytes of each block +} mi_heap_area_t; + +typedef bool (mi_cdecl mi_block_visit_fun)(const mi_heap_t* heap, const mi_heap_area_t* area, void* block, size_t block_size, void* arg); + +mi_decl_export bool mi_heap_visit_blocks(const mi_heap_t* heap, bool visit_all_blocks, mi_block_visit_fun* visitor, void* arg); + +// Experimental +mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export bool mi_is_redirected() mi_attr_noexcept; + +mi_decl_export int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs) mi_attr_noexcept; +mi_decl_export int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs) mi_attr_noexcept; + +// deprecated +mi_decl_export int mi_reserve_huge_os_pages(size_t pages, double max_secs, size_t* pages_reserved) mi_attr_noexcept; + + +// ------------------------------------------------------ +// Convenience +// ------------------------------------------------------ + +#define mi_malloc_tp(tp) ((tp*)mi_malloc(sizeof(tp))) +#define mi_zalloc_tp(tp) ((tp*)mi_zalloc(sizeof(tp))) +#define mi_calloc_tp(tp,n) ((tp*)mi_calloc(n,sizeof(tp))) +#define mi_mallocn_tp(tp,n) ((tp*)mi_mallocn(n,sizeof(tp))) +#define mi_reallocn_tp(p,tp,n) ((tp*)mi_reallocn(p,n,sizeof(tp))) +#define mi_recalloc_tp(p,tp,n) ((tp*)mi_recalloc(p,n,sizeof(tp))) + +#define mi_heap_malloc_tp(hp,tp) ((tp*)mi_heap_malloc(hp,sizeof(tp))) +#define mi_heap_zalloc_tp(hp,tp) ((tp*)mi_heap_zalloc(hp,sizeof(tp))) +#define mi_heap_calloc_tp(hp,tp,n) ((tp*)mi_heap_calloc(hp,n,sizeof(tp))) +#define mi_heap_mallocn_tp(hp,tp,n) ((tp*)mi_heap_mallocn(hp,n,sizeof(tp))) +#define mi_heap_reallocn_tp(hp,p,tp,n) ((tp*)mi_heap_reallocn(hp,p,n,sizeof(tp))) +#define mi_heap_recalloc_tp(hp,p,tp,n) ((tp*)mi_heap_recalloc(hp,p,n,sizeof(tp))) + + +// ------------------------------------------------------ +// Options, all `false` by default +// ------------------------------------------------------ + +typedef enum mi_option_e { + // stable options + mi_option_show_errors, + mi_option_show_stats, + mi_option_verbose, + // the following options are experimental + mi_option_eager_commit, + mi_option_eager_region_commit, + mi_option_reset_decommits, + mi_option_large_os_pages, // implies eager commit + mi_option_reserve_huge_os_pages, + mi_option_segment_cache, + mi_option_page_reset, + mi_option_abandoned_page_reset, + mi_option_segment_reset, + mi_option_eager_commit_delay, + mi_option_reset_delay, + mi_option_use_numa_nodes, + mi_option_os_tag, + mi_option_max_errors, + _mi_option_last +} mi_option_t; + + +mi_decl_nodiscard mi_decl_export bool mi_option_is_enabled(mi_option_t option); +mi_decl_export void mi_option_enable(mi_option_t option); +mi_decl_export void mi_option_disable(mi_option_t option); +mi_decl_export void mi_option_set_enabled(mi_option_t option, bool enable); +mi_decl_export void mi_option_set_enabled_default(mi_option_t option, bool enable); + +mi_decl_nodiscard mi_decl_export long mi_option_get(mi_option_t option); +mi_decl_export void mi_option_set(mi_option_t option, long value); +mi_decl_export void mi_option_set_default(mi_option_t option, long value); + + +// ------------------------------------------------------------------------------------------------------- +// "mi" prefixed implementations of various posix, Unix, Windows, and C++ allocation functions. +// (This can be convenient when providing overrides of these functions as done in `mimalloc-override.h`.) +// note: we use `mi_cfree` as "checked free" and it checks if the pointer is in our heap before free-ing. +// ------------------------------------------------------------------------------------------------------- + +mi_decl_export void mi_cfree(void* p) mi_attr_noexcept; +mi_decl_export void* mi__expand(void* p, size_t newsize) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export size_t mi_malloc_size(const void* p) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept; + +mi_decl_export int mi_posix_memalign(void** p, size_t alignment, size_t size) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_memalign(size_t alignment, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_valloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_pvalloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_aligned_alloc(size_t alignment, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(1); + +mi_decl_nodiscard mi_decl_export void* mi_reallocarray(void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(2,3); +mi_decl_nodiscard mi_decl_export void* mi_aligned_recalloc(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export void* mi_aligned_offset_recalloc(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept; + +mi_decl_nodiscard mi_decl_export mi_decl_restrict unsigned short* mi_wcsdup(const unsigned short* s) mi_attr_noexcept mi_attr_malloc; +mi_decl_nodiscard mi_decl_export mi_decl_restrict unsigned char* mi_mbsdup(const unsigned char* s) mi_attr_noexcept mi_attr_malloc; +mi_decl_export int mi_dupenv_s(char** buf, size_t* size, const char* name) mi_attr_noexcept; +mi_decl_export int mi_wdupenv_s(unsigned short** buf, size_t* size, const unsigned short* name) mi_attr_noexcept; + +mi_decl_export void mi_free_size(void* p, size_t size) mi_attr_noexcept; +mi_decl_export void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept; +mi_decl_export void mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept; + +// The `mi_new` wrappers implement C++ semantics on out-of-memory instead of directly returning `NULL`. +// (and call `std::get_new_handler` and potentially raise a `std::bad_alloc` exception). +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new(size_t size) mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_aligned(size_t size, size_t alignment) mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_nothrow(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_aligned_nothrow(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_n(size_t count, size_t size) mi_attr_malloc mi_attr_alloc_size2(1, 2); +mi_decl_nodiscard mi_decl_export void* mi_new_realloc(void* p, size_t newsize) mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export void* mi_new_reallocn(void* p, size_t newcount, size_t size) mi_attr_alloc_size2(2, 3); + +#ifdef __cplusplus +} +#endif + +// --------------------------------------------------------------------------------------------- +// Implement the C++ std::allocator interface for use in STL containers. +// (note: see `mimalloc-new-delete.h` for overriding the new/delete operators globally) +// --------------------------------------------------------------------------------------------- +#ifdef __cplusplus + +#include // PTRDIFF_MAX +#if (__cplusplus >= 201103L) || (_MSC_VER > 1900) // C++11 +#include // std::true_type +#include // std::forward +#endif + +template struct mi_stl_allocator { + typedef T value_type; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef value_type& reference; + typedef value_type const& const_reference; + typedef value_type* pointer; + typedef value_type const* const_pointer; + template struct rebind { typedef mi_stl_allocator other; }; + + mi_stl_allocator() mi_attr_noexcept = default; + mi_stl_allocator(const mi_stl_allocator&) mi_attr_noexcept = default; + template mi_stl_allocator(const mi_stl_allocator&) mi_attr_noexcept { } + mi_stl_allocator select_on_container_copy_construction() const { return *this; } + void deallocate(T* p, size_type) { mi_free(p); } + + #if (__cplusplus >= 201703L) // C++17 + mi_decl_nodiscard T* allocate(size_type count) { return static_cast(mi_new_n(count, sizeof(T))); } + mi_decl_nodiscard T* allocate(size_type count, const void*) { return allocate(count); } + #else + mi_decl_nodiscard pointer allocate(size_type count, const void* = 0) { return static_cast(mi_new_n(count, sizeof(value_type))); } + #endif + + #if ((__cplusplus >= 201103L) || (_MSC_VER > 1900)) // C++11 + using propagate_on_container_copy_assignment = std::true_type; + using propagate_on_container_move_assignment = std::true_type; + using propagate_on_container_swap = std::true_type; + using is_always_equal = std::true_type; + template void construct(U* p, Args&& ...args) { ::new(p) U(std::forward(args)...); } + template void destroy(U* p) mi_attr_noexcept { p->~U(); } + #else + void construct(pointer p, value_type const& val) { ::new(p) value_type(val); } + void destroy(pointer p) { p->~value_type(); } + #endif + + size_type max_size() const mi_attr_noexcept { return (PTRDIFF_MAX/sizeof(value_type)); } + pointer address(reference x) const { return &x; } + const_pointer address(const_reference x) const { return &x; } +}; + +template bool operator==(const mi_stl_allocator& , const mi_stl_allocator& ) mi_attr_noexcept { return true; } +template bool operator!=(const mi_stl_allocator& , const mi_stl_allocator& ) mi_attr_noexcept { return false; } +#endif // __cplusplus + +#endif diff --git a/extlib/mimalloc/readme.md b/extlib/mimalloc/readme.md new file mode 100644 index 0000000..daf57f3 --- /dev/null +++ b/extlib/mimalloc/readme.md @@ -0,0 +1,583 @@ + + + +[](https://dev.azure.com/Daan0324/mimalloc/_build?definitionId=1&_a=summary) + +# mimalloc + +  + +mimalloc (pronounced "me-malloc") +is a general purpose allocator with excellent [performance](#performance) characteristics. +Initially developed by Daan Leijen for the run-time systems of the +[Koka](https://github.com/koka-lang/koka) and [Lean](https://github.com/leanprover/lean) languages. +Latest release:`v1.6.4` (2020-08-06). + +It is a drop-in replacement for `malloc` and can be used in other programs +without code changes, for example, on dynamically linked ELF-based systems (Linux, BSD, etc.) you can use it as: +``` +> LD_PRELOAD=/usr/bin/libmimalloc.so myprogram +``` +It also has an easy way to override the allocator in [Windows](#override_on_windows). Notable aspects of the design include: + +- __small and consistent__: the library is about 6k LOC using simple and + consistent data structures. This makes it very suitable + to integrate and adapt in other projects. For runtime systems it + provides hooks for a monotonic _heartbeat_ and deferred freeing (for + bounded worst-case times with reference counting). +- __free list sharding__: the big idea: instead of one big free list (per size class) we have + many smaller lists per memory "page" which both reduces fragmentation + and increases locality -- + things that are allocated close in time get allocated close in memory. + (A memory "page" in _mimalloc_ contains blocks of one size class and is + usually 64KiB on a 64-bit system). +- __eager page reset__: when a "page" becomes empty (with increased chance + due to free list sharding) the memory is marked to the OS as unused ("reset" or "purged") + reducing (real) memory pressure and fragmentation, especially in long running + programs. +- __secure__: _mimalloc_ can be built in secure mode, adding guard pages, + randomized allocation, encrypted free lists, etc. to protect against various + heap vulnerabilities. The performance penalty is usually around 10% on average + over our benchmarks. +- __first-class heaps__: efficiently create and use multiple heaps to allocate across different regions. + A heap can be destroyed at once instead of deallocating each object separately. +- __bounded__: it does not suffer from _blowup_ \[1\], has bounded worst-case allocation + times (_wcat_), bounded space overhead (~0.2% meta-data, with at most 12.5% waste in allocation sizes), + and has no internal points of contention using only atomic operations. +- __fast__: In our benchmarks (see [below](#performance)), + _mimalloc_ outperforms other leading allocators (_jemalloc_, _tcmalloc_, _Hoard_, etc), + and usually uses less memory (up to 25% more in the worst case). A nice property + is that it does consistently well over a wide range of benchmarks. There is also good huge OS page + support for larger server programs. + +The [documentation](https://microsoft.github.io/mimalloc) gives a full overview of the API. +You can read more on the design of _mimalloc_ in the [technical report](https://www.microsoft.com/en-us/research/publication/mimalloc-free-list-sharding-in-action) which also has detailed benchmark results. + +Enjoy! + +### Releases + +* 2020-08-06, `v1.6.4`: stable release 1.6: improved error recovery in low-memory situations, + support for IllumOS and Haiku, NUMA support for Vista/XP, improved NUMA detection for AMD Ryzen, ubsan support. +* 2020-05-05, `v1.6.3`: stable release 1.6: improved behavior in out-of-memory situations, improved malloc zones on macOS, + build PIC static libraries by default, add option to abort on out-of-memory, line buffered statistics. +* 2020-04-20, `v1.6.2`: stable release 1.6: fix compilation on Android, MingW, Raspberry, and Conda, + stability fix for Windows 7, fix multiple mimalloc instances in one executable, fix `strnlen` overload, + fix aligned debug padding. +* 2020-02-17, `v1.6.1`: stable release 1.6: minor updates (build with clang-cl, fix alignment issue for small objects). +* 2020-02-09, `v1.6.0`: stable release 1.6: fixed potential memory leak, improved overriding + and thread local support on FreeBSD, NetBSD, DragonFly, and macOSX. New byte-precise + heap block overflow detection in debug mode (besides the double-free detection and free-list + corruption detection). Add `nodiscard` attribute to most allocation functions. + Enable `MIMALLOC_PAGE_RESET` by default. New reclamation strategy for abandoned heap pages + for better memory footprint. +* 2020-02-09, `v1.5.0`: stable release 1.5: improved free performance, small bug fixes. +* 2020-01-22, `v1.4.0`: stable release 1.4: improved performance for delayed OS page reset, +more eager concurrent free, addition of STL allocator, fixed potential memory leak. +* 2020-01-15, `v1.3.0`: stable release 1.3: bug fixes, improved randomness and [stronger +free list encoding](https://github.com/microsoft/mimalloc/blob/783e3377f79ee82af43a0793910a9f2d01ac7863/include/mimalloc-internal.h#L396) in secure mode. +* 2019-12-22, `v1.2.2`: stable release 1.2: minor updates. +* 2019-11-22, `v1.2.0`: stable release 1.2: bug fixes, improved secure mode (free list corruption checks, double free mitigation). Improved dynamic overriding on Windows. +* 2019-10-07, `v1.1.0`: stable release 1.1. +* 2019-09-01, `v1.0.8`: pre-release 8: more robust windows dynamic overriding, initial huge page support. +* 2019-08-10, `v1.0.6`: pre-release 6: various performance improvements. + +Special thanks to: + +* Jason Gibson (@jasongibson) for exhaustive testing on large workloads and server environments and finding complex bugs in (early versions of) `mimalloc`. +* Manuel Pöter (@mpoeter) and Sam Gross (@colesbury) for finding an ABA concurrency issue in abandoned segment reclamation. + +# Building + +## Windows + +Open `ide/vs2019/mimalloc.sln` in Visual Studio 2019 and build (or `ide/vs2017/mimalloc.sln`). +The `mimalloc` project builds a static library (in `out/msvc-x64`), while the +`mimalloc-override` project builds a DLL for overriding malloc +in the entire program. + +## macOS, Linux, BSD, etc. + +We use [`cmake`](https://cmake.org)1 as the build system: + +``` +> mkdir -p out/release +> cd out/release +> cmake ../.. +> make +``` +This builds the library as a shared (dynamic) +library (`.so` or `.dylib`), a static library (`.a`), and +as a single object file (`.o`). + +`> sudo make install` (install the library and header files in `/usr/local/lib` and `/usr/local/include`) + +You can build the debug version which does many internal checks and +maintains detailed statistics as: + +``` +> mkdir -p out/debug +> cd out/debug +> cmake -DCMAKE_BUILD_TYPE=Debug ../.. +> make +``` +This will name the shared library as `libmimalloc-debug.so`. + +Finally, you can build a _secure_ version that uses guard pages, encrypted +free lists, etc., as: +``` +> mkdir -p out/secure +> cd out/secure +> cmake -DMI_SECURE=ON ../.. +> make +``` +This will name the shared library as `libmimalloc-secure.so`. +Use `ccmake`2 instead of `cmake` +to see and customize all the available build options. + +Notes: +1. Install CMake: `sudo apt-get install cmake` +2. Install CCMake: `sudo apt-get install cmake-curses-gui` + + + +# Using the library + +The preferred usage is including ``, linking with +the shared- or static library, and using the `mi_malloc` API exclusively for allocation. For example, +``` +> gcc -o myprogram -lmimalloc myfile.c +``` + +mimalloc uses only safe OS calls (`mmap` and `VirtualAlloc`) and can co-exist +with other allocators linked to the same program. +If you use `cmake`, you can simply use: +``` +find_package(mimalloc 1.4 REQUIRED) +``` +in your `CMakeLists.txt` to find a locally installed mimalloc. Then use either: +``` +target_link_libraries(myapp PUBLIC mimalloc) +``` +to link with the shared (dynamic) library, or: +``` +target_link_libraries(myapp PUBLIC mimalloc-static) +``` +to link with the static library. See `test\CMakeLists.txt` for an example. + +For best performance in C++ programs, it is also recommended to override the +global `new` and `delete` operators. For convience, mimalloc provides +[`mimalloc-new-delete.h`](https://github.com/microsoft/mimalloc/blob/master/include/mimalloc-new-delete.h) which does this for you -- just include it in a single(!) source file in your project. +In C++, mimalloc also provides the `mi_stl_allocator` struct which implements the `std::allocator` +interface. + +You can pass environment variables to print verbose messages (`MIMALLOC_VERBOSE=1`) +and statistics (`MIMALLOC_SHOW_STATS=1`) (in the debug version): +``` +> env MIMALLOC_SHOW_STATS=1 ./cfrac 175451865205073170563711388363 + +175451865205073170563711388363 = 374456281610909315237213 * 468551 + +heap stats: peak total freed unit +normal 2: 16.4 kb 17.5 mb 17.5 mb 16 b ok +normal 3: 16.3 kb 15.2 mb 15.2 mb 24 b ok +normal 4: 64 b 4.6 kb 4.6 kb 32 b ok +normal 5: 80 b 118.4 kb 118.4 kb 40 b ok +normal 6: 48 b 48 b 48 b 48 b ok +normal 17: 960 b 960 b 960 b 320 b ok + +heap stats: peak total freed unit + normal: 33.9 kb 32.8 mb 32.8 mb 1 b ok + huge: 0 b 0 b 0 b 1 b ok + total: 33.9 kb 32.8 mb 32.8 mb 1 b ok +malloc requested: 32.8 mb + + committed: 58.2 kb 58.2 kb 58.2 kb 1 b ok + reserved: 2.0 mb 2.0 mb 2.0 mb 1 b ok + reset: 0 b 0 b 0 b 1 b ok + segments: 1 1 1 +-abandoned: 0 + pages: 6 6 6 +-abandoned: 0 + mmaps: 3 + mmap fast: 0 + mmap slow: 1 + threads: 0 + elapsed: 2.022s + process: user: 1.781s, system: 0.016s, faults: 756, reclaims: 0, rss: 2.7 mb +``` + +The above model of using the `mi_` prefixed API is not always possible +though in existing programs that already use the standard malloc interface, +and another option is to override the standard malloc interface +completely and redirect all calls to the _mimalloc_ library instead . + +## Environment Options + +You can set further options either programmatically (using [`mi_option_set`](https://microsoft.github.io/mimalloc/group__options.html)), +or via environment variables. + +- `MIMALLOC_SHOW_STATS=1`: show statistics when the program terminates. +- `MIMALLOC_VERBOSE=1`: show verbose messages. +- `MIMALLOC_SHOW_ERRORS=1`: show error and warning messages. +- `MIMALLOC_PAGE_RESET=0`: by default, mimalloc will reset (or purge) OS pages that are not in use, to signal to the OS + that the underlying physical memory can be reused. This can reduce memory fragmentation in long running (server) + programs. By setting it to `0` this will no longer be done which can improve performance for batch-like programs. + As an alternative, the `MIMALLOC_RESET_DELAY=` can be set higher (100ms by default) to make the page + reset occur less frequently instead of turning it off completely. +- `MIMALLOC_USE_NUMA_NODES=N`: pretend there are at most `N` NUMA nodes. If not set, the actual NUMA nodes are detected + at runtime. Setting `N` to 1 may avoid problems in some virtual environments. Also, setting it to a lower number than + the actual NUMA nodes is fine and will only cause threads to potentially allocate more memory across actual NUMA + nodes (but this can happen in any case as NUMA local allocation is always a best effort but not guaranteed). +- `MIMALLOC_LARGE_OS_PAGES=1`: use large OS pages (2MiB) when available; for some workloads this can significantly + improve performance. Use `MIMALLOC_VERBOSE` to check if the large OS pages are enabled -- usually one needs + to explicitly allow large OS pages (as on [Windows][windows-huge] and [Linux][linux-huge]). However, sometimes + the OS is very slow to reserve contiguous physical memory for large OS pages so use with care on systems that + can have fragmented memory (for that reason, we generally recommend to use `MIMALLOC_RESERVE_HUGE_OS_PAGES` instead whenever possible). + +- `MIMALLOC_RESERVE_HUGE_OS_PAGES=N`: where N is the number of 1GiB _huge_ OS pages. This reserves the huge pages at + startup and sometimes this can give a large (latency) performance improvement on big workloads. + Usually it is better to not use + `MIMALLOC_LARGE_OS_PAGES` in combination with this setting. Just like large OS pages, use with care as reserving + contiguous physical memory can take a long time when memory is fragmented (but reserving the huge pages is done at + startup only once). + Note that we usually need to explicitly enable huge OS pages (as on [Windows][windows-huge] and [Linux][linux-huge])). + With huge OS pages, it may be beneficial to set the setting + `MIMALLOC_EAGER_COMMIT_DELAY=N` (`N` is 1 by default) to delay the initial `N` segments (of 4MiB) + of a thread to not allocate in the huge OS pages; this prevents threads that are short lived + and allocate just a little to take up space in the huge OS page area (which cannot be reset). + +Use caution when using `fork` in combination with either large or huge OS pages: on a fork, the OS uses copy-on-write +for all pages in the original process including the huge OS pages. When any memory is now written in that area, the +OS will copy the entire 1GiB huge page (or 2MiB large page) which can cause the memory usage to grow in big increments. + +[linux-huge]: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/5/html/tuning_and_optimizing_red_hat_enterprise_linux_for_oracle_9i_and_10g_databases/sect-oracle_9i_and_10g_tuning_guide-large_memory_optimization_big_pages_and_huge_pages-configuring_huge_pages_in_red_hat_enterprise_linux_4_or_5 +[windows-huge]: https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows?view=sql-server-2017 + +## Secure Mode + +_mimalloc_ can be build in secure mode by using the `-DMI_SECURE=ON` flags in `cmake`. This build enables various mitigations +to make mimalloc more robust against exploits. In particular: + +- All internal mimalloc pages are surrounded by guard pages and the heap metadata is behind a guard page as well (so a buffer overflow + exploit cannot reach into the metadata), +- All free list pointers are + [encoded](https://github.com/microsoft/mimalloc/blob/783e3377f79ee82af43a0793910a9f2d01ac7863/include/mimalloc-internal.h#L396) + with per-page keys which is used both to prevent overwrites with a known pointer, as well as to detect heap corruption, +- Double free's are detected (and ignored), +- The free lists are initialized in a random order and allocation randomly chooses between extension and reuse within a page to + mitigate against attacks that rely on a predicable allocation order. Similarly, the larger heap blocks allocated by mimalloc + from the OS are also address randomized. + +As always, evaluate with care as part of an overall security strategy as all of the above are mitigations but not guarantees. + +## Debug Mode + +When _mimalloc_ is built using debug mode, various checks are done at runtime to catch development errors. + +- Statistics are maintained in detail for each object size. They can be shown using `MIMALLOC_SHOW_STATS=1` at runtime. +- All objects have padding at the end to detect (byte precise) heap block overflows. +- Double free's, and freeing invalid heap pointers are detected. +- Corrupted free-lists and some forms of use-after-free are detected. + + +# Overriding Malloc + +Overriding the standard `malloc` can be done either _dynamically_ or _statically_. + +## Dynamic override + +This is the recommended way to override the standard malloc interface. + +### Override on Linux, BSD + +On these ELF-based systems we preload the mimalloc shared +library so all calls to the standard `malloc` interface are +resolved to the _mimalloc_ library. +``` +> env LD_PRELOAD=/usr/lib/libmimalloc.so myprogram +``` + +You can set extra environment variables to check that mimalloc is running, +like: +``` +> env MIMALLOC_VERBOSE=1 LD_PRELOAD=/usr/lib/libmimalloc.so myprogram +``` +or run with the debug version to get detailed statistics: +``` +> env MIMALLOC_SHOW_STATS=1 LD_PRELOAD=/usr/lib/libmimalloc-debug.so myprogram +``` + +### Override on MacOS + +On macOS we can also preload the mimalloc shared +library so all calls to the standard `malloc` interface are +resolved to the _mimalloc_ library. +``` +> env DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=/usr/lib/libmimalloc.dylib myprogram +``` + +Note that certain security restrictions may apply when doing this from +the [shell](https://stackoverflow.com/questions/43941322/dyld-insert-libraries-ignored-when-calling-application-through-bash). + +(Note: macOS support for dynamic overriding is recent, please report any issues.) + +### Override on Windows + +Overriding on Windows is robust and has the +particular advantage to be able to redirect all malloc/free calls that go through +the (dynamic) C runtime allocator, including those from other DLL's or libraries. + +The overriding on Windows requires that you link your program explicitly with +the mimalloc DLL and use the C-runtime library as a DLL (using the `/MD` or `/MDd` switch). +Also, the `mimalloc-redirect.dll` (or `mimalloc-redirect32.dll`) must be available +in the same folder as the main `mimalloc-override.dll` at runtime (as it is a dependency). +The redirection DLL ensures that all calls to the C runtime malloc API get redirected to +mimalloc (in `mimalloc-override.dll`). + +To ensure the mimalloc DLL is loaded at run-time it is easiest to insert some +call to the mimalloc API in the `main` function, like `mi_version()` +(or use the `/INCLUDE:mi_version` switch on the linker). See the `mimalloc-override-test` project +for an example on how to use this. For best performance on Windows with C++, it +is also recommended to also override the `new`/`delete` operations (by including +[`mimalloc-new-delete.h`](https://github.com/microsoft/mimalloc/blob/master/include/mimalloc-new-delete.h) a single(!) source file in your project). + +The environment variable `MIMALLOC_DISABLE_REDIRECT=1` can be used to disable dynamic +overriding at run-time. Use `MIMALLOC_VERBOSE=1` to check if mimalloc was successfully redirected. + +(Note: in principle, it is possible to even patch existing executables without any recompilation +if they are linked with the dynamic C runtime (`ucrtbase.dll`) -- just put the `mimalloc-override.dll` +into the import table (and put `mimalloc-redirect.dll` in the same folder) +Such patching can be done for example with [CFF Explorer](https://ntcore.com/?page_id=388)). + + +## Static override + +On Unix-like systems, you can also statically link with _mimalloc_ to override the standard +malloc interface. The recommended way is to link the final program with the +_mimalloc_ single object file (`mimalloc-override.o`). We use +an object file instead of a library file as linkers give preference to +that over archives to resolve symbols. To ensure that the standard +malloc interface resolves to the _mimalloc_ library, link it as the first +object file. For example: +``` +> gcc -o myprogram mimalloc-override.o myfile1.c ... +``` + +Another way to override statically that works on all platforms, is to +link statically to mimalloc (as shown in the introduction) and include a +header file in each source file that re-defines `malloc` etc. to `mi_malloc`. +This is provided by [`mimalloc-override.h`](https://github.com/microsoft/mimalloc/blob/master/include/mimalloc-override.h). This only works reliably though if all sources are +under your control or otherwise mixing of pointers from different heaps may occur! + + +# Performance + +Last update: 2020-01-20 + +We tested _mimalloc_ against many other top allocators over a wide +range of benchmarks, ranging from various real world programs to +synthetic benchmarks that see how the allocator behaves under more +extreme circumstances. In our benchmark suite, _mimalloc_ outperforms other leading +allocators (_jemalloc_, _tcmalloc_, _Hoard_, etc), and has a similar memory footprint. A nice property is that it +does consistently well over the wide range of benchmarks. + +General memory allocators are interesting as there exists no algorithm that is +optimal -- for a given allocator one can usually construct a workload +where it does not do so well. The goal is thus to find an allocation +strategy that performs well over a wide range of benchmarks without +suffering from (too much) underperformance in less common situations. + +As always, interpret these results with care since some benchmarks test synthetic +or uncommon situations that may never apply to your workloads. For example, most +allocators do not do well on `xmalloc-testN` but that includes the best +industrial allocators like _jemalloc_ and _tcmalloc_ that are used in some of +the world's largest systems (like Chrome or FreeBSD). + +We show here only an overview -- for +more specific details and further benchmarks we refer to the +[technical report](https://www.microsoft.com/en-us/research/publication/mimalloc-free-list-sharding-in-action). +The benchmark suite is automated and available separately +as [mimalloc-bench](https://github.com/daanx/mimalloc-bench). + + +## Benchmark Results on 36-core Intel + +Testing on a big Amazon EC2 compute instance +([c5.18xlarge](https://aws.amazon.com/ec2/instance-types/#Compute_Optimized)) +consisting of a 72 processor Intel Xeon at 3GHz +with 144GiB ECC memory, running Ubuntu 18.04.1 with glibc 2.27 and GCC 7.4.0. +The measured allocators are _mimalloc_ (xmi, tag:v1.4.0, page reset enabled) +and its secure build as _smi_, +Google's [_tcmalloc_](https://github.com/gperftools/gperftools) (tc, tag:gperftools-2.7) used in Chrome, +Facebook's [_jemalloc_](https://github.com/jemalloc/jemalloc) (je, tag:5.2.1) by Jason Evans used in Firefox and FreeBSD, +the Intel thread building blocks [allocator](https://github.com/intel/tbb) (tbb, tag:2020), +[rpmalloc](https://github.com/mjansson/rpmalloc) (rp,tag:1.4.0) by Mattias Jansson, +the original scalable [_Hoard_](https://github.com/emeryberger/Hoard) (tag:3.13) allocator by Emery Berger \[1], +the memory compacting [_Mesh_](https://github.com/plasma-umass/Mesh) (git:51222e7) allocator by +Bobby Powers _et al_ \[8], +and finally the default system allocator (glibc, 2.27) (based on _PtMalloc2_). + + + + +Any benchmarks ending in `N` run on all processors in parallel. +Results are averaged over 10 runs and reported relative +to mimalloc (where 1.2 means it took 1.2× longer to run). +The legend also contains the _overall relative score_ between the +allocators where 100 points is the maximum if an allocator is fastest on +all benchmarks. + +The single threaded _cfrac_ benchmark by Dave Barrett is an implementation of +continued fraction factorization which uses many small short-lived allocations. +All allocators do well on such common usage, where _mimalloc_ is just a tad +faster than _tcmalloc_ and +_jemalloc_. + +The _leanN_ program is interesting as a large realistic and +concurrent workload of the [Lean](https://github.com/leanprover/lean) +theorem prover compiling its own standard library, and there is a 7% +speedup over _tcmalloc_. This is +quite significant: if Lean spends 20% of its time in the +allocator that means that _mimalloc_ is 1.3× faster than _tcmalloc_ +here. (This is surprising as that is not measured in a pure +allocation benchmark like _alloc-test_. We conjecture that we see this +outsized improvement here because _mimalloc_ has better locality in +the allocation which improves performance for the *other* computations +in a program as well). + +The single threaded _redis_ benchmark again show that most allocators do well on such workloads where _tcmalloc_ +did best this time. + +The _larsonN_ server benchmark by Larson and Krishnan \[2] allocates and frees between threads. They observed this +behavior (which they call _bleeding_) in actual server applications, and the benchmark simulates this. +Here, _mimalloc_ is quite a bit faster than _tcmalloc_ and _jemalloc_ probably due to the object migration between different threads. + +The _mstressN_ workload performs many allocations and re-allocations, +and migrates objects between threads (as in _larsonN_). However, it also +creates and destroys the _N_ worker threads a few times keeping some objects +alive beyond the life time of the allocating thread. We observed this +behavior in many larger server applications. + +The [_rptestN_](https://github.com/mjansson/rpmalloc-benchmark) benchmark +by Mattias Jansson is a allocator test originally designed +for _rpmalloc_, and tries to simulate realistic allocation patterns over +multiple threads. Here the differences between allocators become more apparent. + +The second benchmark set tests specific aspects of the allocators and +shows even more extreme differences between them. + +The _alloc-test_, by +[OLogN Technologies AG](http://ithare.com/testing-memory-allocators-ptmalloc2-tcmalloc-hoard-jemalloc-while-trying-to-simulate-real-world-loads/), is a very allocation intensive benchmark doing millions of +allocations in various size classes. The test is scaled such that when an +allocator performs almost identically on _alloc-test1_ as _alloc-testN_ it +means that it scales linearly. Here, _tcmalloc_, and +_Hoard_ seem to scale less well and do more than 10% worse on the multi-core version. Even the best industrial +allocators (_tcmalloc_, _jemalloc_, and _tbb_) are more than 10% slower as _mimalloc_ here. + +The _sh6bench_ and _sh8bench_ benchmarks are +developed by [MicroQuill](http://www.microquill.com/) as part of SmartHeap. +In _sh6bench_ _mimalloc_ does much +better than the others (more than 1.5× faster than _jemalloc_). +We cannot explain this well but believe it is +caused in part by the "reverse" free-ing pattern in _sh6bench_. +The _sh8bench_ is a variation with object migration +between threads; whereas _tcmalloc_ did well on _sh6bench_, the addition of object migration causes it to be 10× slower than before. + +The _xmalloc-testN_ benchmark by Lever and Boreham \[5] and Christian Eder, simulates an asymmetric workload where +some threads only allocate, and others only free -- they observed this pattern in +larger server applications. Here we see that +the _mimalloc_ technique of having non-contended sharded thread free +lists pays off as it outperforms others by a very large margin. Only _rpmalloc_ and _tbb_ also scale well on this benchmark. + +The _cache-scratch_ benchmark by Emery Berger \[1], and introduced with +the Hoard allocator to test for _passive-false_ sharing of cache lines. +With a single thread they all +perform the same, but when running with multiple threads the potential allocator +induced false sharing of the cache lines can cause large run-time differences. +Crundal \[6] describes in detail why the false cache line sharing occurs in the _tcmalloc_ design, and also discusses how this +can be avoided with some small implementation changes. +Only the _tbb_, _rpmalloc_ and _mesh_ allocators also avoid the +cache line sharing completely, while _Hoard_ and _glibc_ seem to mitigate +the effects. Kukanov and Voss \[7] describe in detail +how the design of _tbb_ avoids the false cache line sharing. + +## On 24-core AMD Epyc + +For completeness, here are the results on a +[r5a.12xlarge](https://aws.amazon.com/ec2/instance-types/#Memory_Optimized) instance +having a 48 processor AMD Epyc 7000 at 2.5GHz with 384GiB of memory. +The results are similar to the Intel results but it is interesting to +see the differences in the _larsonN_, _mstressN_, and _xmalloc-testN_ benchmarks. + + + + + +## Peak Working Set + +The following figure shows the peak working set (rss) of the allocators +on the benchmarks (on the c5.18xlarge instance). + + + + +Note that the _xmalloc-testN_ memory usage should be disregarded as it +allocates more the faster the program runs. Similarly, memory usage of +_mstressN_, _rptestN_ and _sh8bench_ can vary depending on scheduling and +speed. Nevertheless, even though _mimalloc_ is fast on these benchmarks we +believe the memory usage is too high and hope to improve. + + +# References + +- \[1] Emery D. Berger, Kathryn S. McKinley, Robert D. Blumofe, and Paul R. Wilson. + _Hoard: A Scalable Memory Allocator for Multithreaded Applications_ + the Ninth International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS-IX). Cambridge, MA, November 2000. + [pdf](http://www.cs.utexas.edu/users/mckinley/papers/asplos-2000.pdf) + +- \[2] P. Larson and M. Krishnan. _Memory allocation for long-running server applications_. + In ISMM, Vancouver, B.C., Canada, 1998. [pdf](http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.45.1947&rep=rep1&type=pdf) + +- \[3] D. Grunwald, B. Zorn, and R. Henderson. + _Improving the cache locality of memory allocation_. In R. Cartwright, editor, + Proceedings of the Conference on Programming Language Design and Implementation, pages 177–186, New York, NY, USA, June 1993. [pdf](http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.43.6621&rep=rep1&type=pdf) + +- \[4] J. Barnes and P. Hut. _A hierarchical O(n*log(n)) force-calculation algorithm_. Nature, 324:446-449, 1986. + +- \[5] C. Lever, and D. Boreham. _Malloc() Performance in a Multithreaded Linux Environment._ + In USENIX Annual Technical Conference, Freenix Session. San Diego, CA. Jun. 2000. + Available at + +- \[6] Timothy Crundal. _Reducing Active-False Sharing in TCMalloc_. 2016. CS16S1 project at the Australian National University. [pdf](http://courses.cecs.anu.edu.au/courses/CSPROJECTS/16S1/Reports/Timothy_Crundal_Report.pdf) + +- \[7] Alexey Kukanov, and Michael J Voss. + _The Foundations for Scalable Multi-Core Software in Intel Threading Building Blocks._ + Intel Technology Journal 11 (4). 2007 + +- \[8] Bobby Powers, David Tench, Emery D. Berger, and Andrew McGregor. + _Mesh: Compacting Memory Management for C/C++_ + In Proceedings of the 40th ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI'19), June 2019, pages 333-–346. + + + + +# Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. diff --git a/extlib/mimalloc/src/alloc-aligned.c b/extlib/mimalloc/src/alloc-aligned.c new file mode 100644 index 0000000..ca16d36 --- /dev/null +++ b/extlib/mimalloc/src/alloc-aligned.c @@ -0,0 +1,205 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +#include "mimalloc.h" +#include "mimalloc-internal.h" + +#include // memset, memcpy + +// ------------------------------------------------------ +// Aligned Allocation +// ------------------------------------------------------ + +static void* mi_heap_malloc_zero_aligned_at(mi_heap_t* const heap, const size_t size, const size_t alignment, const size_t offset, const bool zero) mi_attr_noexcept { + // note: we don't require `size > offset`, we just guarantee that + // the address at offset is aligned regardless of the allocated size. + mi_assert(alignment > 0); + if (mi_unlikely(size > PTRDIFF_MAX)) return NULL; // we don't allocate more than PTRDIFF_MAX (see ) + if (mi_unlikely(alignment==0 || !_mi_is_power_of_two(alignment))) return NULL; // require power-of-two (see ) + const uintptr_t align_mask = alignment-1; // for any x, `(x & align_mask) == (x % alignment)` + + // try if there is a small block available with just the right alignment + const size_t padsize = size + MI_PADDING_SIZE; + if (mi_likely(padsize <= MI_SMALL_SIZE_MAX)) { + mi_page_t* page = _mi_heap_get_free_small_page(heap,padsize); + const bool is_aligned = (((uintptr_t)page->free+offset) & align_mask)==0; + if (mi_likely(page->free != NULL && is_aligned)) + { + #if MI_STAT>1 + mi_heap_stat_increase( heap, malloc, size); + #endif + void* p = _mi_page_malloc(heap,page,padsize); // TODO: inline _mi_page_malloc + mi_assert_internal(p != NULL); + mi_assert_internal(((uintptr_t)p + offset) % alignment == 0); + if (zero) _mi_block_zero_init(page,p,size); + return p; + } + } + + // use regular allocation if it is guaranteed to fit the alignment constraints + if (offset==0 && alignment<=padsize && padsize<=MI_MEDIUM_OBJ_SIZE_MAX && (padsize&align_mask)==0) { + void* p = _mi_heap_malloc_zero(heap, size, zero); + mi_assert_internal(p == NULL || ((uintptr_t)p % alignment) == 0); + return p; + } + + // otherwise over-allocate + void* p = _mi_heap_malloc_zero(heap, size + alignment - 1, zero); + if (p == NULL) return NULL; + + // .. and align within the allocation + uintptr_t adjust = alignment - (((uintptr_t)p + offset) & align_mask); + mi_assert_internal(adjust <= alignment); + void* aligned_p = (adjust == alignment ? p : (void*)((uintptr_t)p + adjust)); + if (aligned_p != p) mi_page_set_has_aligned(_mi_ptr_page(p), true); + mi_assert_internal(((uintptr_t)aligned_p + offset) % alignment == 0); + mi_assert_internal( p == _mi_page_ptr_unalign(_mi_ptr_segment(aligned_p),_mi_ptr_page(aligned_p),aligned_p) ); + return aligned_p; +} + + +mi_decl_restrict void* mi_heap_malloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_malloc_zero_aligned_at(heap, size, alignment, offset, false); +} + +mi_decl_restrict void* mi_heap_malloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_malloc_aligned_at(heap, size, alignment, 0); +} + +mi_decl_restrict void* mi_heap_zalloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_malloc_zero_aligned_at(heap, size, alignment, offset, true); +} + +mi_decl_restrict void* mi_heap_zalloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_zalloc_aligned_at(heap, size, alignment, 0); +} + +mi_decl_restrict void* mi_heap_calloc_aligned_at(mi_heap_t* heap, size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(count, size, &total)) return NULL; + return mi_heap_zalloc_aligned_at(heap, total, alignment, offset); +} + +mi_decl_restrict void* mi_heap_calloc_aligned(mi_heap_t* heap, size_t count, size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_calloc_aligned_at(heap,count,size,alignment,0); +} + +mi_decl_restrict void* mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_malloc_aligned_at(mi_get_default_heap(), size, alignment, offset); +} + +mi_decl_restrict void* mi_malloc_aligned(size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_malloc_aligned(mi_get_default_heap(), size, alignment); +} + +mi_decl_restrict void* mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_zalloc_aligned_at(mi_get_default_heap(), size, alignment, offset); +} + +mi_decl_restrict void* mi_zalloc_aligned(size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_zalloc_aligned(mi_get_default_heap(), size, alignment); +} + +mi_decl_restrict void* mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_calloc_aligned_at(mi_get_default_heap(), count, size, alignment, offset); +} + +mi_decl_restrict void* mi_calloc_aligned(size_t count, size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_calloc_aligned(mi_get_default_heap(), count, size, alignment); +} + + +static void* mi_heap_realloc_zero_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset, bool zero) mi_attr_noexcept { + mi_assert(alignment > 0); + if (alignment <= sizeof(uintptr_t)) return _mi_heap_realloc_zero(heap,p,newsize,zero); + if (p == NULL) return mi_heap_malloc_zero_aligned_at(heap,newsize,alignment,offset,zero); + size_t size = mi_usable_size(p); + if (newsize <= size && newsize >= (size - (size / 2)) + && (((uintptr_t)p + offset) % alignment) == 0) { + return p; // reallocation still fits, is aligned and not more than 50% waste + } + else { + void* newp = mi_heap_malloc_aligned_at(heap,newsize,alignment,offset); + if (newp != NULL) { + if (zero && newsize > size) { + const mi_page_t* page = _mi_ptr_page(newp); + if (page->is_zero) { + // already zero initialized + mi_assert_expensive(mi_mem_is_zero(newp,newsize)); + } + else { + // also set last word in the previous allocation to zero to ensure any padding is zero-initialized + size_t start = (size >= sizeof(intptr_t) ? size - sizeof(intptr_t) : 0); + memset((uint8_t*)newp + start, 0, newsize - start); + } + } + memcpy(newp, p, (newsize > size ? size : newsize)); + mi_free(p); // only free if successful + } + return newp; + } +} + +static void* mi_heap_realloc_zero_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, bool zero) mi_attr_noexcept { + mi_assert(alignment > 0); + if (alignment <= sizeof(uintptr_t)) return _mi_heap_realloc_zero(heap,p,newsize,zero); + size_t offset = ((uintptr_t)p % alignment); // use offset of previous allocation (p can be NULL) + return mi_heap_realloc_zero_aligned_at(heap,p,newsize,alignment,offset,zero); +} + +void* mi_heap_realloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_realloc_zero_aligned_at(heap,p,newsize,alignment,offset,false); +} + +void* mi_heap_realloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept { + return mi_heap_realloc_zero_aligned(heap,p,newsize,alignment,false); +} + +void* mi_heap_rezalloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_realloc_zero_aligned_at(heap, p, newsize, alignment, offset, true); +} + +void* mi_heap_rezalloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept { + return mi_heap_realloc_zero_aligned(heap, p, newsize, alignment, true); +} + +void* mi_heap_recalloc_aligned_at(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(newcount, size, &total)) return NULL; + return mi_heap_rezalloc_aligned_at(heap, p, total, alignment, offset); +} + +void* mi_heap_recalloc_aligned(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(newcount, size, &total)) return NULL; + return mi_heap_rezalloc_aligned(heap, p, total, alignment); +} + +void* mi_realloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_realloc_aligned_at(mi_get_default_heap(), p, newsize, alignment, offset); +} + +void* mi_realloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept { + return mi_heap_realloc_aligned(mi_get_default_heap(), p, newsize, alignment); +} + +void* mi_rezalloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_rezalloc_aligned_at(mi_get_default_heap(), p, newsize, alignment, offset); +} + +void* mi_rezalloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept { + return mi_heap_rezalloc_aligned(mi_get_default_heap(), p, newsize, alignment); +} + +void* mi_recalloc_aligned_at(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_recalloc_aligned_at(mi_get_default_heap(), p, newcount, size, alignment, offset); +} + +void* mi_recalloc_aligned(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_recalloc_aligned(mi_get_default_heap(), p, newcount, size, alignment); +} + diff --git a/extlib/mimalloc/src/alloc-override-osx.c b/extlib/mimalloc/src/alloc-override-osx.c new file mode 100644 index 0000000..c1c880c --- /dev/null +++ b/extlib/mimalloc/src/alloc-override-osx.c @@ -0,0 +1,260 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +#include "mimalloc.h" +#include "mimalloc-internal.h" + +#if defined(MI_MALLOC_OVERRIDE) + +#if !defined(__APPLE__) +#error "this file should only be included on macOS" +#endif + +/* ------------------------------------------------------ + Override system malloc on macOS + This is done through the malloc zone interface. + It seems we also need to interpose (see `alloc-override.c`) + or otherwise we get zone errors as there are usually + already allocations done by the time we take over the + zone. Unfortunately, that means we need to replace + the `free` with a checked free (`cfree`) impacting + performance. +------------------------------------------------------ */ + +#include +#include +#include // memset + +#if defined(MAC_OS_X_VERSION_10_6) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 +// only available from OSX 10.6 +extern malloc_zone_t* malloc_default_purgeable_zone(void) __attribute__((weak_import)); +#endif + + +/* ------------------------------------------------------ + malloc zone members +------------------------------------------------------ */ + +static size_t zone_size(malloc_zone_t* zone, const void* p) { + UNUSED(zone); + if (!mi_is_in_heap_region(p)) + return 0; // not our pointer, bail out + + return mi_usable_size(p); +} + +static void* zone_malloc(malloc_zone_t* zone, size_t size) { + UNUSED(zone); + return mi_malloc(size); +} + +static void* zone_calloc(malloc_zone_t* zone, size_t count, size_t size) { + UNUSED(zone); + return mi_calloc(count, size); +} + +static void* zone_valloc(malloc_zone_t* zone, size_t size) { + UNUSED(zone); + return mi_malloc_aligned(size, _mi_os_page_size()); +} + +static void zone_free(malloc_zone_t* zone, void* p) { + UNUSED(zone); + return mi_free(p); +} + +static void* zone_realloc(malloc_zone_t* zone, void* p, size_t newsize) { + UNUSED(zone); + return mi_realloc(p, newsize); +} + +static void* zone_memalign(malloc_zone_t* zone, size_t alignment, size_t size) { + UNUSED(zone); + return mi_malloc_aligned(size,alignment); +} + +static void zone_destroy(malloc_zone_t* zone) { + UNUSED(zone); + // todo: ignore for now? +} + +static unsigned zone_batch_malloc(malloc_zone_t* zone, size_t size, void** ps, unsigned count) { + size_t i; + for (i = 0; i < count; i++) { + ps[i] = zone_malloc(zone, size); + if (ps[i] == NULL) break; + } + return i; +} + +static void zone_batch_free(malloc_zone_t* zone, void** ps, unsigned count) { + for(size_t i = 0; i < count; i++) { + zone_free(zone, ps[i]); + ps[i] = NULL; + } +} + +static size_t zone_pressure_relief(malloc_zone_t* zone, size_t size) { + UNUSED(zone); UNUSED(size); + mi_collect(false); + return 0; +} + +static void zone_free_definite_size(malloc_zone_t* zone, void* p, size_t size) { + UNUSED(size); + zone_free(zone,p); +} + + +/* ------------------------------------------------------ + Introspection members +------------------------------------------------------ */ + +static kern_return_t intro_enumerator(task_t task, void* p, + unsigned type_mask, vm_address_t zone_address, + memory_reader_t reader, + vm_range_recorder_t recorder) +{ + // todo: enumerate all memory + UNUSED(task); UNUSED(p); UNUSED(type_mask); UNUSED(zone_address); + UNUSED(reader); UNUSED(recorder); + return KERN_SUCCESS; +} + +static size_t intro_good_size(malloc_zone_t* zone, size_t size) { + UNUSED(zone); + return mi_good_size(size); +} + +static boolean_t intro_check(malloc_zone_t* zone) { + UNUSED(zone); + return true; +} + +static void intro_print(malloc_zone_t* zone, boolean_t verbose) { + UNUSED(zone); UNUSED(verbose); + mi_stats_print(NULL); +} + +static void intro_log(malloc_zone_t* zone, void* p) { + UNUSED(zone); UNUSED(p); + // todo? +} + +static void intro_force_lock(malloc_zone_t* zone) { + UNUSED(zone); + // todo? +} + +static void intro_force_unlock(malloc_zone_t* zone) { + UNUSED(zone); + // todo? +} + +static void intro_statistics(malloc_zone_t* zone, malloc_statistics_t* stats) { + UNUSED(zone); + // todo... + stats->blocks_in_use = 0; + stats->size_in_use = 0; + stats->max_size_in_use = 0; + stats->size_allocated = 0; +} + +static boolean_t intro_zone_locked(malloc_zone_t* zone) { + UNUSED(zone); + return false; +} + + +/* ------------------------------------------------------ + At process start, override the default allocator +------------------------------------------------------ */ + +static malloc_zone_t* mi_get_default_zone() +{ + // The first returned zone is the real default + malloc_zone_t** zones = NULL; + unsigned count = 0; + kern_return_t ret = malloc_get_all_zones(0, NULL, (vm_address_t**)&zones, &count); + if (ret == KERN_SUCCESS && count > 0) { + return zones[0]; + } + else { + // fallback + return malloc_default_zone(); + } +} + +static void __attribute__((constructor)) _mi_macos_override_malloc() +{ + static malloc_introspection_t intro; + memset(&intro, 0, sizeof(intro)); + + intro.enumerator = &intro_enumerator; + intro.good_size = &intro_good_size; + intro.check = &intro_check; + intro.print = &intro_print; + intro.log = &intro_log; + intro.force_lock = &intro_force_lock; + intro.force_unlock = &intro_force_unlock; + + static malloc_zone_t zone; + memset(&zone, 0, sizeof(zone)); + + zone.version = 4; + zone.zone_name = "mimalloc"; + zone.size = &zone_size; + zone.introspect = &intro; + zone.malloc = &zone_malloc; + zone.calloc = &zone_calloc; + zone.valloc = &zone_valloc; + zone.free = &zone_free; + zone.realloc = &zone_realloc; + zone.destroy = &zone_destroy; + zone.batch_malloc = &zone_batch_malloc; + zone.batch_free = &zone_batch_free; + + malloc_zone_t* purgeable_zone = NULL; + +#if defined(MAC_OS_X_VERSION_10_6) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + // switch to version 9 on OSX 10.6 to support memalign. + zone.version = 9; + zone.memalign = &zone_memalign; + zone.free_definite_size = &zone_free_definite_size; + zone.pressure_relief = &zone_pressure_relief; + intro.zone_locked = &intro_zone_locked; + intro.statistics = &intro_statistics; + + // force the purgeable zone to exist to avoid strange bugs + if (malloc_default_purgeable_zone) { + purgeable_zone = malloc_default_purgeable_zone(); + } +#endif + + // Register our zone + malloc_zone_register(&zone); + + // Unregister the default zone, this makes our zone the new default + // as that was the last registered. + malloc_zone_t *default_zone = mi_get_default_zone(); + malloc_zone_unregister(default_zone); + + // Reregister the default zone so free and realloc in that zone keep working. + malloc_zone_register(default_zone); + + // Unregister, and re-register the purgeable_zone to avoid bugs if it occurs + // earlier than the default zone. + if (purgeable_zone != NULL) { + malloc_zone_unregister(purgeable_zone); + malloc_zone_register(purgeable_zone); + } + +} + +#endif // MI_MALLOC_OVERRIDE diff --git a/extlib/mimalloc/src/alloc-override.c b/extlib/mimalloc/src/alloc-override.c new file mode 100644 index 0000000..ae7ad7d --- /dev/null +++ b/extlib/mimalloc/src/alloc-override.c @@ -0,0 +1,214 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +#if !defined(MI_IN_ALLOC_C) +#error "this file should be included from 'alloc.c' (so aliases can work)" +#endif + +#if defined(MI_MALLOC_OVERRIDE) && defined(_WIN32) && !(defined(MI_SHARED_LIB) && defined(_DLL)) +#error "It is only possible to override "malloc" on Windows when building as a DLL (and linking the C runtime as a DLL)" +#endif + +#if defined(MI_MALLOC_OVERRIDE) && !(defined(_WIN32)) // || (defined(__MACH__) && !defined(MI_INTERPOSE))) + +// ------------------------------------------------------ +// Override system malloc +// ------------------------------------------------------ + +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__MACH__) + // use aliasing to alias the exported function to one of our `mi_` functions + #if (defined(__GNUC__) && __GNUC__ >= 9) + #define MI_FORWARD(fun) __attribute__((alias(#fun), used, visibility("default"), copy(fun))) + #else + #define MI_FORWARD(fun) __attribute__((alias(#fun), used, visibility("default"))) + #endif + #define MI_FORWARD1(fun,x) MI_FORWARD(fun) + #define MI_FORWARD2(fun,x,y) MI_FORWARD(fun) + #define MI_FORWARD3(fun,x,y,z) MI_FORWARD(fun) + #define MI_FORWARD0(fun,x) MI_FORWARD(fun) + #define MI_FORWARD02(fun,x,y) MI_FORWARD(fun) +#else + // use forwarding by calling our `mi_` function + #define MI_FORWARD1(fun,x) { return fun(x); } + #define MI_FORWARD2(fun,x,y) { return fun(x,y); } + #define MI_FORWARD3(fun,x,y,z) { return fun(x,y,z); } + #define MI_FORWARD0(fun,x) { fun(x); } + #define MI_FORWARD02(fun,x,y) { fun(x,y); } +#endif + +#if defined(__APPLE__) && defined(MI_SHARED_LIB_EXPORT) && defined(MI_INTERPOSE) + // use interposing so `DYLD_INSERT_LIBRARIES` works without `DYLD_FORCE_FLAT_NAMESPACE=1` + // See: + struct mi_interpose_s { + const void* replacement; + const void* target; + }; + #define MI_INTERPOSE_FUN(oldfun,newfun) { (const void*)&newfun, (const void*)&oldfun } + #define MI_INTERPOSE_MI(fun) MI_INTERPOSE_FUN(fun,mi_##fun) + __attribute__((used)) static struct mi_interpose_s _mi_interposes[] __attribute__((section("__DATA, __interpose"))) = + { + MI_INTERPOSE_MI(malloc), + MI_INTERPOSE_MI(calloc), + MI_INTERPOSE_MI(realloc), + MI_INTERPOSE_MI(strdup), + MI_INTERPOSE_MI(strndup), + MI_INTERPOSE_MI(realpath), + MI_INTERPOSE_MI(posix_memalign), + MI_INTERPOSE_MI(reallocf), + MI_INTERPOSE_MI(valloc), + // some code allocates from a zone but deallocates using plain free :-( (like NxHashResizeToCapacity ) + MI_INTERPOSE_FUN(free,mi_cfree), // use safe free that checks if pointers are from us + }; +#elif defined(_MSC_VER) + // cannot override malloc unless using a dll. + // we just override new/delete which does work in a static library. +#else + // On all other systems forward to our API + void* malloc(size_t size) MI_FORWARD1(mi_malloc, size); + void* calloc(size_t size, size_t n) MI_FORWARD2(mi_calloc, size, n); + void* realloc(void* p, size_t newsize) MI_FORWARD2(mi_realloc, p, newsize); + void free(void* p) MI_FORWARD0(mi_free, p); +#endif + +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__MACH__) +#pragma GCC visibility push(default) +#endif + +// ------------------------------------------------------ +// Override new/delete +// This is not really necessary as they usually call +// malloc/free anyway, but it improves performance. +// ------------------------------------------------------ +#ifdef __cplusplus + // ------------------------------------------------------ + // With a C++ compiler we override the new/delete operators. + // see + // ------------------------------------------------------ + #include + void operator delete(void* p) noexcept MI_FORWARD0(mi_free,p); + void operator delete[](void* p) noexcept MI_FORWARD0(mi_free,p); + + void* operator new(std::size_t n) noexcept(false) MI_FORWARD1(mi_new,n); + void* operator new[](std::size_t n) noexcept(false) MI_FORWARD1(mi_new,n); + + void* operator new (std::size_t n, const std::nothrow_t& tag) noexcept { UNUSED(tag); return mi_new_nothrow(n); } + void* operator new[](std::size_t n, const std::nothrow_t& tag) noexcept { UNUSED(tag); return mi_new_nothrow(n); } + + #if (__cplusplus >= 201402L || _MSC_VER >= 1916) + void operator delete (void* p, std::size_t n) noexcept MI_FORWARD02(mi_free_size,p,n); + void operator delete[](void* p, std::size_t n) noexcept MI_FORWARD02(mi_free_size,p,n); + #endif + + #if (__cplusplus > 201402L && defined(__cpp_aligned_new)) && (!defined(__GNUC__) || (__GNUC__ > 5)) + void operator delete (void* p, std::align_val_t al) noexcept { mi_free_aligned(p, static_cast(al)); } + void operator delete[](void* p, std::align_val_t al) noexcept { mi_free_aligned(p, static_cast(al)); } + void operator delete (void* p, std::size_t n, std::align_val_t al) noexcept { mi_free_size_aligned(p, n, static_cast(al)); }; + void operator delete[](void* p, std::size_t n, std::align_val_t al) noexcept { mi_free_size_aligned(p, n, static_cast(al)); }; + + void* operator new( std::size_t n, std::align_val_t al) noexcept(false) { return mi_new_aligned(n, static_cast(al)); } + void* operator new[]( std::size_t n, std::align_val_t al) noexcept(false) { return mi_new_aligned(n, static_cast(al)); } + void* operator new (std::size_t n, std::align_val_t al, const std::nothrow_t&) noexcept { return mi_new_aligned_nothrow(n, static_cast(al)); } + void* operator new[](std::size_t n, std::align_val_t al, const std::nothrow_t&) noexcept { return mi_new_aligned_nothrow(n, static_cast(al)); } + #endif + +#elif (defined(__GNUC__) || defined(__clang__)) + // ------------------------------------------------------ + // Override by defining the mangled C++ names of the operators (as + // used by GCC and CLang). + // See + // ------------------------------------------------------ + void _ZdlPv(void* p) MI_FORWARD0(mi_free,p); // delete + void _ZdaPv(void* p) MI_FORWARD0(mi_free,p); // delete[] + void _ZdlPvm(void* p, size_t n) MI_FORWARD02(mi_free_size,p,n); + void _ZdaPvm(void* p, size_t n) MI_FORWARD02(mi_free_size,p,n); + void _ZdlPvSt11align_val_t(void* p, size_t al) { mi_free_aligned(p,al); } + void _ZdaPvSt11align_val_t(void* p, size_t al) { mi_free_aligned(p,al); } + void _ZdlPvmSt11align_val_t(void* p, size_t n, size_t al) { mi_free_size_aligned(p,n,al); } + void _ZdaPvmSt11align_val_t(void* p, size_t n, size_t al) { mi_free_size_aligned(p,n,al); } + + typedef struct mi_nothrow_s { } mi_nothrow_t; + #if (MI_INTPTR_SIZE==8) + void* _Znwm(size_t n) MI_FORWARD1(mi_new,n); // new 64-bit + void* _Znam(size_t n) MI_FORWARD1(mi_new,n); // new[] 64-bit + void* _ZnwmSt11align_val_t(size_t n, size_t al) MI_FORWARD2(mi_new_aligned, n, al); + void* _ZnamSt11align_val_t(size_t n, size_t al) MI_FORWARD2(mi_new_aligned, n, al); + void* _ZnwmRKSt9nothrow_t(size_t n, mi_nothrow_t tag) { UNUSED(tag); return mi_new_nothrow(n); } + void* _ZnamRKSt9nothrow_t(size_t n, mi_nothrow_t tag) { UNUSED(tag); return mi_new_nothrow(n); } + void* _ZnwmSt11align_val_tRKSt9nothrow_t(size_t n, size_t al, mi_nothrow_t tag) { UNUSED(tag); return mi_new_aligned_nothrow(n,al); } + void* _ZnamSt11align_val_tRKSt9nothrow_t(size_t n, size_t al, mi_nothrow_t tag) { UNUSED(tag); return mi_new_aligned_nothrow(n,al); } + #elif (MI_INTPTR_SIZE==4) + void* _Znwj(size_t n) MI_FORWARD1(mi_new,n); // new 64-bit + void* _Znaj(size_t n) MI_FORWARD1(mi_new,n); // new[] 64-bit + void* _ZnwjSt11align_val_t(size_t n, size_t al) MI_FORWARD2(mi_new_aligned, n, al); + void* _ZnajSt11align_val_t(size_t n, size_t al) MI_FORWARD2(mi_new_aligned, n, al); + void* _ZnwjRKSt9nothrow_t(size_t n, mi_nothrow_t tag) { UNUSED(tag); return mi_new_nothrow(n); } + void* _ZnajRKSt9nothrow_t(size_t n, mi_nothrow_t tag) { UNUSED(tag); return mi_new_nothrow(n); } + void* _ZnwjSt11align_val_tRKSt9nothrow_t(size_t n, size_t al, mi_nothrow_t tag) { UNUSED(tag); return mi_new_aligned_nothrow(n,al); } + void* _ZnajSt11align_val_tRKSt9nothrow_t(size_t n, size_t al, mi_nothrow_t tag) { UNUSED(tag); return mi_new_aligned_nothrow(n,al); } + #else + #error "define overloads for new/delete for this platform (just for performance, can be skipped)" + #endif +#endif // __cplusplus + + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------------ +// Posix & Unix functions definitions +// ------------------------------------------------------ + +void cfree(void* p) MI_FORWARD0(mi_free, p); +void* reallocf(void* p, size_t newsize) MI_FORWARD2(mi_reallocf,p,newsize); +size_t malloc_size(const void* p) MI_FORWARD1(mi_usable_size,p); +#if !defined(__ANDROID__) +size_t malloc_usable_size(void *p) MI_FORWARD1(mi_usable_size,p); +#else +size_t malloc_usable_size(const void *p) MI_FORWARD1(mi_usable_size,p); +#endif + +// no forwarding here due to aliasing/name mangling issues +void* valloc(size_t size) { return mi_valloc(size); } +void* pvalloc(size_t size) { return mi_pvalloc(size); } +void* reallocarray(void* p, size_t count, size_t size) { return mi_reallocarray(p, count, size); } +void* memalign(size_t alignment, size_t size) { return mi_memalign(alignment, size); } +int posix_memalign(void** p, size_t alignment, size_t size) { return mi_posix_memalign(p, alignment, size); } +void* _aligned_malloc(size_t alignment, size_t size) { return mi_aligned_alloc(alignment, size); } + +// on some glibc `aligned_alloc` is declared `static inline` so we cannot override it (e.g. Conda). This happens +// when _GLIBCXX_HAVE_ALIGNED_ALLOC is not defined. However, in those cases it will use `memalign`, `posix_memalign`, +// or `_aligned_malloc` and we can avoid overriding it ourselves. +// We should always override if using C compilation. (issue #276) +#if _GLIBCXX_HAVE_ALIGNED_ALLOC || !defined(__cplusplus) +void* aligned_alloc(size_t alignment, size_t size) { return mi_aligned_alloc(alignment, size); } +#endif + + +#if defined(__GLIBC__) && defined(__linux__) + // forward __libc interface (needed for glibc-based Linux distributions) + void* __libc_malloc(size_t size) MI_FORWARD1(mi_malloc,size); + void* __libc_calloc(size_t count, size_t size) MI_FORWARD2(mi_calloc,count,size); + void* __libc_realloc(void* p, size_t size) MI_FORWARD2(mi_realloc,p,size); + void __libc_free(void* p) MI_FORWARD0(mi_free,p); + void __libc_cfree(void* p) MI_FORWARD0(mi_free,p); + + void* __libc_valloc(size_t size) { return mi_valloc(size); } + void* __libc_pvalloc(size_t size) { return mi_pvalloc(size); } + void* __libc_memalign(size_t alignment, size_t size) { return mi_memalign(alignment,size); } + int __posix_memalign(void** p, size_t alignment, size_t size) { return mi_posix_memalign(p,alignment,size); } +#endif + +#ifdef __cplusplus +} +#endif + +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__MACH__) +#pragma GCC visibility pop +#endif + +#endif // MI_MALLOC_OVERRIDE && !_WIN32 diff --git a/extlib/mimalloc/src/alloc-posix.c b/extlib/mimalloc/src/alloc-posix.c new file mode 100644 index 0000000..4395893 --- /dev/null +++ b/extlib/mimalloc/src/alloc-posix.c @@ -0,0 +1,155 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018,2019, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +// ------------------------------------------------------------------------ +// mi prefixed publi definitions of various Posix, Unix, and C++ functions +// for convenience and used when overriding these functions. +// ------------------------------------------------------------------------ +#include "mimalloc.h" +#include "mimalloc-internal.h" + +// ------------------------------------------------------ +// Posix & Unix functions definitions +// ------------------------------------------------------ + +#include +#include // memcpy +#include // getenv + +#ifndef EINVAL +#define EINVAL 22 +#endif +#ifndef ENOMEM +#define ENOMEM 12 +#endif + + +size_t mi_malloc_size(const void* p) mi_attr_noexcept { + return mi_usable_size(p); +} + +size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept { + return mi_usable_size(p); +} + +void mi_cfree(void* p) mi_attr_noexcept { + if (mi_is_in_heap_region(p)) { + mi_free(p); + } +} + +int mi_posix_memalign(void** p, size_t alignment, size_t size) mi_attr_noexcept { + // Note: The spec dictates we should not modify `*p` on an error. (issue#27) + // + if (p == NULL) return EINVAL; + if (alignment % sizeof(void*) != 0) return EINVAL; // natural alignment + if (!_mi_is_power_of_two(alignment)) return EINVAL; // not a power of 2 + void* q = (mi_malloc_satisfies_alignment(alignment, size) ? mi_malloc(size) : mi_malloc_aligned(size, alignment)); + if (q==NULL && size != 0) return ENOMEM; + mi_assert_internal(((uintptr_t)q % alignment) == 0); + *p = q; + return 0; +} + +mi_decl_restrict void* mi_memalign(size_t alignment, size_t size) mi_attr_noexcept { + void* p = (mi_malloc_satisfies_alignment(alignment,size) ? mi_malloc(size) : mi_malloc_aligned(size, alignment)); + mi_assert_internal(((uintptr_t)p % alignment) == 0); + return p; +} + +mi_decl_restrict void* mi_valloc(size_t size) mi_attr_noexcept { + return mi_memalign( _mi_os_page_size(), size ); +} + +mi_decl_restrict void* mi_pvalloc(size_t size) mi_attr_noexcept { + size_t psize = _mi_os_page_size(); + if (size >= SIZE_MAX - psize) return NULL; // overflow + size_t asize = _mi_align_up(size, psize); + return mi_malloc_aligned(asize, psize); +} + +mi_decl_restrict void* mi_aligned_alloc(size_t alignment, size_t size) mi_attr_noexcept { + if (alignment==0 || !_mi_is_power_of_two(alignment)) return NULL; + if ((size&(alignment-1)) != 0) return NULL; // C11 requires integral multiple, see + void* p = (mi_malloc_satisfies_alignment(alignment, size) ? mi_malloc(size) : mi_malloc_aligned(size, alignment)); + mi_assert_internal(((uintptr_t)p % alignment) == 0); + return p; +} + +void* mi_reallocarray( void* p, size_t count, size_t size ) mi_attr_noexcept { // BSD + void* newp = mi_reallocn(p,count,size); + if (newp==NULL) errno = ENOMEM; + return newp; +} + +void* mi__expand(void* p, size_t newsize) mi_attr_noexcept { // Microsoft + void* res = mi_expand(p, newsize); + if (res == NULL) errno = ENOMEM; + return res; +} + +mi_decl_restrict unsigned short* mi_wcsdup(const unsigned short* s) mi_attr_noexcept { + if (s==NULL) return NULL; + size_t len; + for(len = 0; s[len] != 0; len++) { } + size_t size = (len+1)*sizeof(unsigned short); + unsigned short* p = (unsigned short*)mi_malloc(size); + if (p != NULL) { + memcpy(p,s,size); + } + return p; +} + +mi_decl_restrict unsigned char* mi_mbsdup(const unsigned char* s) mi_attr_noexcept { + return (unsigned char*)mi_strdup((const char*)s); +} + +int mi_dupenv_s(char** buf, size_t* size, const char* name) mi_attr_noexcept { + if (buf==NULL || name==NULL) return EINVAL; + if (size != NULL) *size = 0; + #pragma warning(suppress:4996) + char* p = getenv(name); + if (p==NULL) { + *buf = NULL; + } + else { + *buf = mi_strdup(p); + if (*buf==NULL) return ENOMEM; + if (size != NULL) *size = strlen(p); + } + return 0; +} + +int mi_wdupenv_s(unsigned short** buf, size_t* size, const unsigned short* name) mi_attr_noexcept { + if (buf==NULL || name==NULL) return EINVAL; + if (size != NULL) *size = 0; +#if !defined(_WIN32) || (defined(WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP)) + // not supported + *buf = NULL; + return EINVAL; +#else + #pragma warning(suppress:4996) + unsigned short* p = (unsigned short*)_wgetenv((const wchar_t*)name); + if (p==NULL) { + *buf = NULL; + } + else { + *buf = mi_wcsdup(p); + if (*buf==NULL) return ENOMEM; + if (size != NULL) *size = wcslen((const wchar_t*)p); + } + return 0; +#endif +} + +void* mi_aligned_offset_recalloc(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { // Microsoft + return mi_recalloc_aligned_at(p, newcount, size, alignment, offset); +} + +void* mi_aligned_recalloc(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept { // Microsoft + return mi_recalloc_aligned(p, newcount, size, alignment); +} diff --git a/extlib/mimalloc/src/alloc.c b/extlib/mimalloc/src/alloc.c new file mode 100644 index 0000000..5703452 --- /dev/null +++ b/extlib/mimalloc/src/alloc.c @@ -0,0 +1,857 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc-internal.h" +#include "mimalloc-atomic.h" + +#include // memset, memcpy, strlen +#include // malloc, exit + +#define MI_IN_ALLOC_C +#include "alloc-override.c" +#undef MI_IN_ALLOC_C + +// ------------------------------------------------------ +// Allocation +// ------------------------------------------------------ + +// Fast allocation in a page: just pop from the free list. +// Fall back to generic allocation only if the list is empty. +extern inline void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept { + mi_assert_internal(page->xblock_size==0||mi_page_block_size(page) >= size); + mi_block_t* block = page->free; + if (mi_unlikely(block == NULL)) { + return _mi_malloc_generic(heap, size); + } + mi_assert_internal(block != NULL && _mi_ptr_page(block) == page); + // pop from the free list + page->free = mi_block_next(page, block); + page->used++; + mi_assert_internal(page->free == NULL || _mi_ptr_page(page->free) == page); +#if (MI_DEBUG>0) + if (!page->is_zero) { memset(block, MI_DEBUG_UNINIT, size); } +#elif (MI_SECURE!=0) + block->next = 0; // don't leak internal data +#endif +#if (MI_STAT>1) + const size_t bsize = mi_page_usable_block_size(page); + if (bsize <= MI_LARGE_OBJ_SIZE_MAX) { + const size_t bin = _mi_bin(bsize); + mi_heap_stat_increase(heap, normal[bin], 1); + } +#endif +#if (MI_PADDING > 0) && defined(MI_ENCODE_FREELIST) + mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + mi_page_usable_block_size(page)); + ptrdiff_t delta = ((uint8_t*)padding - (uint8_t*)block - (size - MI_PADDING_SIZE)); + mi_assert_internal(delta >= 0 && mi_page_usable_block_size(page) >= (size - MI_PADDING_SIZE + delta)); + padding->canary = (uint32_t)(mi_ptr_encode(page,block,page->keys)); + padding->delta = (uint32_t)(delta); + uint8_t* fill = (uint8_t*)padding - delta; + const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // set at most N initial padding bytes + for (size_t i = 0; i < maxpad; i++) { fill[i] = MI_DEBUG_PADDING; } +#endif + return block; +} + +// allocate a small block +extern inline mi_decl_restrict void* mi_heap_malloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept { + mi_assert(heap!=NULL); + mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local + mi_assert(size <= MI_SMALL_SIZE_MAX); + #if (MI_PADDING) + if (size == 0) { + size = sizeof(void*); + } + #endif + mi_page_t* page = _mi_heap_get_free_small_page(heap,size + MI_PADDING_SIZE); + void* p = _mi_page_malloc(heap, page, size + MI_PADDING_SIZE); + mi_assert_internal(p==NULL || mi_usable_size(p) >= size); + #if MI_STAT>1 + if (p != NULL) { + if (!mi_heap_is_initialized(heap)) { heap = mi_get_default_heap(); } + mi_heap_stat_increase(heap, malloc, mi_usable_size(p)); + } + #endif + return p; +} + +extern inline mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept { + return mi_heap_malloc_small(mi_get_default_heap(), size); +} + +// The main allocation function +extern inline mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept { + if (mi_likely(size <= MI_SMALL_SIZE_MAX)) { + return mi_heap_malloc_small(heap, size); + } + else { + mi_assert(heap!=NULL); + mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local + void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE); // note: size can overflow but it is detected in malloc_generic + mi_assert_internal(p == NULL || mi_usable_size(p) >= size); + #if MI_STAT>1 + if (p != NULL) { + if (!mi_heap_is_initialized(heap)) { heap = mi_get_default_heap(); } + mi_heap_stat_increase(heap, malloc, mi_usable_size(p)); + } + #endif + return p; + } +} + +extern inline mi_decl_restrict void* mi_malloc(size_t size) mi_attr_noexcept { + return mi_heap_malloc(mi_get_default_heap(), size); +} + + +void _mi_block_zero_init(const mi_page_t* page, void* p, size_t size) { + // note: we need to initialize the whole usable block size to zero, not just the requested size, + // or the recalloc/rezalloc functions cannot safely expand in place (see issue #63) + UNUSED(size); + mi_assert_internal(p != NULL); + mi_assert_internal(mi_usable_size(p) >= size); // size can be zero + mi_assert_internal(_mi_ptr_page(p)==page); + if (page->is_zero && size > sizeof(mi_block_t)) { + // already zero initialized memory + ((mi_block_t*)p)->next = 0; // clear the free list pointer + mi_assert_expensive(mi_mem_is_zero(p, mi_usable_size(p))); + } + else { + // otherwise memset + memset(p, 0, mi_usable_size(p)); + } +} + +// zero initialized small block +mi_decl_restrict void* mi_zalloc_small(size_t size) mi_attr_noexcept { + void* p = mi_malloc_small(size); + if (p != NULL) { + _mi_block_zero_init(_mi_ptr_page(p), p, size); // todo: can we avoid getting the page again? + } + return p; +} + +void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) { + void* p = mi_heap_malloc(heap,size); + if (zero && p != NULL) { + _mi_block_zero_init(_mi_ptr_page(p),p,size); // todo: can we avoid getting the page again? + } + return p; +} + +extern inline mi_decl_restrict void* mi_heap_zalloc(mi_heap_t* heap, size_t size) mi_attr_noexcept { + return _mi_heap_malloc_zero(heap, size, true); +} + +mi_decl_restrict void* mi_zalloc(size_t size) mi_attr_noexcept { + return mi_heap_zalloc(mi_get_default_heap(),size); +} + + +// ------------------------------------------------------ +// Check for double free in secure and debug mode +// This is somewhat expensive so only enabled for secure mode 4 +// ------------------------------------------------------ + +#if (MI_ENCODE_FREELIST && (MI_SECURE>=4 || MI_DEBUG!=0)) +// linear check if the free list contains a specific element +static bool mi_list_contains(const mi_page_t* page, const mi_block_t* list, const mi_block_t* elem) { + while (list != NULL) { + if (elem==list) return true; + list = mi_block_next(page, list); + } + return false; +} + +static mi_decl_noinline bool mi_check_is_double_freex(const mi_page_t* page, const mi_block_t* block) { + // The decoded value is in the same page (or NULL). + // Walk the free lists to verify positively if it is already freed + if (mi_list_contains(page, page->free, block) || + mi_list_contains(page, page->local_free, block) || + mi_list_contains(page, mi_page_thread_free(page), block)) + { + _mi_error_message(EAGAIN, "double free detected of block %p with size %zu\n", block, mi_page_block_size(page)); + return true; + } + return false; +} + +static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) { + mi_block_t* n = mi_block_nextx(page, block, page->keys); // pretend it is freed, and get the decoded first field + if (((uintptr_t)n & (MI_INTPTR_SIZE-1))==0 && // quick check: aligned pointer? + (n==NULL || mi_is_in_same_page(block, n))) // quick check: in same page or NULL? + { + // Suspicous: decoded value a in block is in the same page (or NULL) -- maybe a double free? + // (continue in separate function to improve code generation) + return mi_check_is_double_freex(page, block); + } + return false; +} +#else +static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) { + UNUSED(page); + UNUSED(block); + return false; +} +#endif + +// --------------------------------------------------------------------------- +// Check for heap block overflow by setting up padding at the end of the block +// --------------------------------------------------------------------------- + +#if (MI_PADDING>0) && defined(MI_ENCODE_FREELIST) +static bool mi_page_decode_padding(const mi_page_t* page, const mi_block_t* block, size_t* delta, size_t* bsize) { + *bsize = mi_page_usable_block_size(page); + const mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + *bsize); + *delta = padding->delta; + return ((uint32_t)mi_ptr_encode(page,block,page->keys) == padding->canary && *delta <= *bsize); +} + +// Return the exact usable size of a block. +static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) { + size_t bsize; + size_t delta; + bool ok = mi_page_decode_padding(page, block, &delta, &bsize); + mi_assert_internal(ok); mi_assert_internal(delta <= bsize); + return (ok ? bsize - delta : 0); +} + +static bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, size_t* size, size_t* wrong) { + size_t bsize; + size_t delta; + bool ok = mi_page_decode_padding(page, block, &delta, &bsize); + *size = *wrong = bsize; + if (!ok) return false; + mi_assert_internal(bsize >= delta); + *size = bsize - delta; + uint8_t* fill = (uint8_t*)block + bsize - delta; + const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // check at most the first N padding bytes + for (size_t i = 0; i < maxpad; i++) { + if (fill[i] != MI_DEBUG_PADDING) { + *wrong = bsize - delta + i; + return false; + } + } + return true; +} + +static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) { + size_t size; + size_t wrong; + if (!mi_verify_padding(page,block,&size,&wrong)) { + _mi_error_message(EFAULT, "buffer overflow in heap block %p of size %zu: write after %zu bytes\n", block, size, wrong ); + } +} + +// When a non-thread-local block is freed, it becomes part of the thread delayed free +// list that is freed later by the owning heap. If the exact usable size is too small to +// contain the pointer for the delayed list, then shrink the padding (by decreasing delta) +// so it will later not trigger an overflow error in `mi_free_block`. +static void mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) { + size_t bsize; + size_t delta; + bool ok = mi_page_decode_padding(page, block, &delta, &bsize); + mi_assert_internal(ok); + if (!ok || (bsize - delta) >= min_size) return; // usually already enough space + mi_assert_internal(bsize >= min_size); + if (bsize < min_size) return; // should never happen + size_t new_delta = (bsize - min_size); + mi_assert_internal(new_delta < bsize); + mi_padding_t* padding = (mi_padding_t*)((uint8_t*)block + bsize); + padding->delta = (uint32_t)new_delta; +} +#else +static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) { + UNUSED(page); + UNUSED(block); +} + +static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) { + UNUSED(block); + return mi_page_usable_block_size(page); +} + +static void mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) { + UNUSED(page); + UNUSED(block); + UNUSED(min_size); +} +#endif + +// ------------------------------------------------------ +// Free +// ------------------------------------------------------ + +// multi-threaded free +static mi_decl_noinline void _mi_free_block_mt(mi_page_t* page, mi_block_t* block) +{ + // The padding check may access the non-thread-owned page for the key values. + // that is safe as these are constant and the page won't be freed (as the block is not freed yet). + mi_check_padding(page, block); + mi_padding_shrink(page, block, sizeof(mi_block_t)); // for small size, ensure we can fit the delayed thread pointers without triggering overflow detection + #if (MI_DEBUG!=0) + memset(block, MI_DEBUG_FREED, mi_usable_size(block)); + #endif + + // huge page segments are always abandoned and can be freed immediately + mi_segment_t* const segment = _mi_page_segment(page); + if (segment->page_kind==MI_PAGE_HUGE) { + _mi_segment_huge_page_free(segment, page, block); + return; + } + + // Try to put the block on either the page-local thread free list, or the heap delayed free list. + mi_thread_free_t tfree; + mi_thread_free_t tfreex; + bool use_delayed; + do { + tfree = mi_atomic_read_relaxed(&page->xthread_free); + use_delayed = (mi_tf_delayed(tfree) == MI_USE_DELAYED_FREE); + if (mi_unlikely(use_delayed)) { + // unlikely: this only happens on the first concurrent free in a page that is in the full list + tfreex = mi_tf_set_delayed(tfree,MI_DELAYED_FREEING); + } + else { + // usual: directly add to page thread_free list + mi_block_set_next(page, block, mi_tf_block(tfree)); + tfreex = mi_tf_set_block(tfree,block); + } + } while (!mi_atomic_cas_weak(&page->xthread_free, tfreex, tfree)); + + if (mi_unlikely(use_delayed)) { + // racy read on `heap`, but ok because MI_DELAYED_FREEING is set (see `mi_heap_delete` and `mi_heap_collect_abandon`) + mi_heap_t* const heap = mi_page_heap(page); + mi_assert_internal(heap != NULL); + if (heap != NULL) { + // add to the delayed free list of this heap. (do this atomically as the lock only protects heap memory validity) + mi_block_t* dfree; + do { + dfree = mi_atomic_read_ptr_relaxed(mi_block_t,&heap->thread_delayed_free); + mi_block_set_nextx(heap,block,dfree, heap->keys); + } while (!mi_atomic_cas_ptr_weak(mi_block_t,&heap->thread_delayed_free, block, dfree)); + } + + // and reset the MI_DELAYED_FREEING flag + do { + tfreex = tfree = mi_atomic_read_relaxed(&page->xthread_free); + mi_assert_internal(mi_tf_delayed(tfree) == MI_DELAYED_FREEING); + tfreex = mi_tf_set_delayed(tfree,MI_NO_DELAYED_FREE); + } while (!mi_atomic_cas_weak(&page->xthread_free, tfreex, tfree)); + } +} + + +// regular free +static inline void _mi_free_block(mi_page_t* page, bool local, mi_block_t* block) +{ + // and push it on the free list + if (mi_likely(local)) { + // owning thread can free a block directly + if (mi_unlikely(mi_check_is_double_free(page, block))) return; + mi_check_padding(page, block); + #if (MI_DEBUG!=0) + memset(block, MI_DEBUG_FREED, mi_page_block_size(page)); + #endif + mi_block_set_next(page, block, page->local_free); + page->local_free = block; + page->used--; + if (mi_unlikely(mi_page_all_free(page))) { + _mi_page_retire(page); + } + else if (mi_unlikely(mi_page_is_in_full(page))) { + _mi_page_unfull(page); + } + } + else { + _mi_free_block_mt(page,block); + } +} + + +// Adjust a block that was allocated aligned, to the actual start of the block in the page. +mi_block_t* _mi_page_ptr_unalign(const mi_segment_t* segment, const mi_page_t* page, const void* p) { + mi_assert_internal(page!=NULL && p!=NULL); + const size_t diff = (uint8_t*)p - _mi_page_start(segment, page, NULL); + const size_t adjust = (diff % mi_page_block_size(page)); + return (mi_block_t*)((uintptr_t)p - adjust); +} + + +static void mi_decl_noinline mi_free_generic(const mi_segment_t* segment, bool local, void* p) { + mi_page_t* const page = _mi_segment_page_of(segment, p); + mi_block_t* const block = (mi_page_has_aligned(page) ? _mi_page_ptr_unalign(segment, page, p) : (mi_block_t*)p); + _mi_free_block(page, local, block); +} + +// Get the segment data belonging to a pointer +// This is just a single `and` in assembly but does further checks in debug mode +// (and secure mode) if this was a valid pointer. +static inline mi_segment_t* mi_checked_ptr_segment(const void* p, const char* msg) +{ + UNUSED(msg); +#if (MI_DEBUG>0) + if (mi_unlikely(((uintptr_t)p & (MI_INTPTR_SIZE - 1)) != 0)) { + _mi_error_message(EINVAL, "%s: invalid (unaligned) pointer: %p\n", msg, p); + return NULL; + } +#endif + + mi_segment_t* const segment = _mi_ptr_segment(p); + if (mi_unlikely(segment == NULL)) return NULL; // checks also for (p==NULL) + +#if (MI_DEBUG>0) + if (mi_unlikely(!mi_is_in_heap_region(p))) { + _mi_warning_message("%s: pointer might not point to a valid heap region: %p\n" + "(this may still be a valid very large allocation (over 64MiB))\n", msg, p); + if (mi_likely(_mi_ptr_cookie(segment) == segment->cookie)) { + _mi_warning_message("(yes, the previous pointer %p was valid after all)\n", p); + } + } +#endif +#if (MI_DEBUG>0 || MI_SECURE>=4) + if (mi_unlikely(_mi_ptr_cookie(segment) != segment->cookie)) { + _mi_error_message(EINVAL, "%s: pointer does not point to a valid heap space: %p\n", p); + } +#endif + return segment; +} + + +// Free a block +void mi_free(void* p) mi_attr_noexcept +{ + const mi_segment_t* const segment = mi_checked_ptr_segment(p,"mi_free"); + if (mi_unlikely(segment == NULL)) return; + + const uintptr_t tid = _mi_thread_id(); + mi_page_t* const page = _mi_segment_page_of(segment, p); + mi_block_t* const block = (mi_block_t*)p; + +#if (MI_STAT>1) + mi_heap_t* const heap = mi_heap_get_default(); + const size_t bsize = mi_page_usable_block_size(page); + mi_heap_stat_decrease(heap, malloc, bsize); + if (bsize <= MI_LARGE_OBJ_SIZE_MAX) { // huge page stats are accounted for in `_mi_page_retire` + mi_heap_stat_decrease(heap, normal[_mi_bin(bsize)], 1); + } +#endif + + if (mi_likely(tid == segment->thread_id && page->flags.full_aligned == 0)) { // the thread id matches and it is not a full page, nor has aligned blocks + // local, and not full or aligned + if (mi_unlikely(mi_check_is_double_free(page,block))) return; + mi_check_padding(page, block); + #if (MI_DEBUG!=0) + memset(block, MI_DEBUG_FREED, mi_page_block_size(page)); + #endif + mi_block_set_next(page, block, page->local_free); + page->local_free = block; + page->used--; + if (mi_unlikely(mi_page_all_free(page))) { + _mi_page_retire(page); + } + } + else { + // non-local, aligned blocks, or a full page; use the more generic path + // note: recalc page in generic to improve code generation + mi_free_generic(segment, tid == segment->thread_id, p); + } +} + +bool _mi_free_delayed_block(mi_block_t* block) { + // get segment and page + const mi_segment_t* const segment = _mi_ptr_segment(block); + mi_assert_internal(_mi_ptr_cookie(segment) == segment->cookie); + mi_assert_internal(_mi_thread_id() == segment->thread_id); + mi_page_t* const page = _mi_segment_page_of(segment, block); + + // Clear the no-delayed flag so delayed freeing is used again for this page. + // This must be done before collecting the free lists on this page -- otherwise + // some blocks may end up in the page `thread_free` list with no blocks in the + // heap `thread_delayed_free` list which may cause the page to be never freed! + // (it would only be freed if we happen to scan it in `mi_page_queue_find_free_ex`) + _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, false /* dont overwrite never delayed */); + + // collect all other non-local frees to ensure up-to-date `used` count + _mi_page_free_collect(page, false); + + // and free the block (possibly freeing the page as well since used is updated) + _mi_free_block(page, true, block); + return true; +} + +// Bytes available in a block +static size_t _mi_usable_size(const void* p, const char* msg) mi_attr_noexcept { + const mi_segment_t* const segment = mi_checked_ptr_segment(p,msg); + if (segment==NULL) return 0; + const mi_page_t* const page = _mi_segment_page_of(segment, p); + const mi_block_t* block = (const mi_block_t*)p; + if (mi_unlikely(mi_page_has_aligned(page))) { + block = _mi_page_ptr_unalign(segment, page, p); + size_t size = mi_page_usable_size_of(page, block); + ptrdiff_t const adjust = (uint8_t*)p - (uint8_t*)block; + mi_assert_internal(adjust >= 0 && (size_t)adjust <= size); + return (size - adjust); + } + else { + return mi_page_usable_size_of(page, block); + } +} + +size_t mi_usable_size(const void* p) mi_attr_noexcept { + return _mi_usable_size(p, "mi_usable_size"); +} + + +// ------------------------------------------------------ +// ensure explicit external inline definitions are emitted! +// ------------------------------------------------------ + +#ifdef __cplusplus +void* _mi_externs[] = { + (void*)&_mi_page_malloc, + (void*)&mi_malloc, + (void*)&mi_malloc_small, + (void*)&mi_heap_malloc, + (void*)&mi_heap_zalloc, + (void*)&mi_heap_malloc_small +}; +#endif + + +// ------------------------------------------------------ +// Allocation extensions +// ------------------------------------------------------ + +void mi_free_size(void* p, size_t size) mi_attr_noexcept { + UNUSED_RELEASE(size); + mi_assert(p == NULL || size <= _mi_usable_size(p,"mi_free_size")); + mi_free(p); +} + +void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept { + UNUSED_RELEASE(alignment); + mi_assert(((uintptr_t)p % alignment) == 0); + mi_free_size(p,size); +} + +void mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept { + UNUSED_RELEASE(alignment); + mi_assert(((uintptr_t)p % alignment) == 0); + mi_free(p); +} + +extern inline mi_decl_restrict void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(count,size,&total)) return NULL; + return mi_heap_zalloc(heap,total); +} + +mi_decl_restrict void* mi_calloc(size_t count, size_t size) mi_attr_noexcept { + return mi_heap_calloc(mi_get_default_heap(),count,size); +} + +// Uninitialized `calloc` +extern mi_decl_restrict void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(count, size, &total)) return NULL; + return mi_heap_malloc(heap, total); +} + +mi_decl_restrict void* mi_mallocn(size_t count, size_t size) mi_attr_noexcept { + return mi_heap_mallocn(mi_get_default_heap(),count,size); +} + +// Expand in place or fail +void* mi_expand(void* p, size_t newsize) mi_attr_noexcept { + if (p == NULL) return NULL; + size_t size = _mi_usable_size(p,"mi_expand"); + if (newsize > size) return NULL; + return p; // it fits +} + +void* _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero) { + if (p == NULL) return _mi_heap_malloc_zero(heap,newsize,zero); + size_t size = _mi_usable_size(p,"mi_realloc"); + if (newsize <= size && newsize >= (size / 2)) { + return p; // reallocation still fits and not more than 50% waste + } + void* newp = mi_heap_malloc(heap,newsize); + if (mi_likely(newp != NULL)) { + if (zero && newsize > size) { + // also set last word in the previous allocation to zero to ensure any padding is zero-initialized + size_t start = (size >= sizeof(intptr_t) ? size - sizeof(intptr_t) : 0); + memset((uint8_t*)newp + start, 0, newsize - start); + } + memcpy(newp, p, (newsize > size ? size : newsize)); + mi_free(p); // only free if successful + } + return newp; +} + +void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept { + return _mi_heap_realloc_zero(heap, p, newsize, false); +} + +void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(count, size, &total)) return NULL; + return mi_heap_realloc(heap, p, total); +} + + +// Reallocate but free `p` on errors +void* mi_heap_reallocf(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept { + void* newp = mi_heap_realloc(heap, p, newsize); + if (newp==NULL && p!=NULL) mi_free(p); + return newp; +} + +void* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept { + return _mi_heap_realloc_zero(heap, p, newsize, true); +} + +void* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(count, size, &total)) return NULL; + return mi_heap_rezalloc(heap, p, total); +} + + +void* mi_realloc(void* p, size_t newsize) mi_attr_noexcept { + return mi_heap_realloc(mi_get_default_heap(),p,newsize); +} + +void* mi_reallocn(void* p, size_t count, size_t size) mi_attr_noexcept { + return mi_heap_reallocn(mi_get_default_heap(),p,count,size); +} + +// Reallocate but free `p` on errors +void* mi_reallocf(void* p, size_t newsize) mi_attr_noexcept { + return mi_heap_reallocf(mi_get_default_heap(),p,newsize); +} + +void* mi_rezalloc(void* p, size_t newsize) mi_attr_noexcept { + return mi_heap_rezalloc(mi_get_default_heap(), p, newsize); +} + +void* mi_recalloc(void* p, size_t count, size_t size) mi_attr_noexcept { + return mi_heap_recalloc(mi_get_default_heap(), p, count, size); +} + + + +// ------------------------------------------------------ +// strdup, strndup, and realpath +// ------------------------------------------------------ + +// `strdup` using mi_malloc +mi_decl_restrict char* mi_heap_strdup(mi_heap_t* heap, const char* s) mi_attr_noexcept { + if (s == NULL) return NULL; + size_t n = strlen(s); + char* t = (char*)mi_heap_malloc(heap,n+1); + if (t != NULL) memcpy(t, s, n + 1); + return t; +} + +mi_decl_restrict char* mi_strdup(const char* s) mi_attr_noexcept { + return mi_heap_strdup(mi_get_default_heap(), s); +} + +// `strndup` using mi_malloc +mi_decl_restrict char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n) mi_attr_noexcept { + if (s == NULL) return NULL; + const char* end = (const char*)memchr(s, 0, n); // find end of string in the first `n` characters (returns NULL if not found) + const size_t m = (end != NULL ? (size_t)(end - s) : n); // `m` is the minimum of `n` or the end-of-string + mi_assert_internal(m <= n); + char* t = (char*)mi_heap_malloc(heap, m+1); + if (t == NULL) return NULL; + memcpy(t, s, m); + t[m] = 0; + return t; +} + +mi_decl_restrict char* mi_strndup(const char* s, size_t n) mi_attr_noexcept { + return mi_heap_strndup(mi_get_default_heap(),s,n); +} + +#ifndef __wasi__ +// `realpath` using mi_malloc +#ifdef _WIN32 +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif +#include +mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept { + // todo: use GetFullPathNameW to allow longer file names + char buf[PATH_MAX]; + DWORD res = GetFullPathNameA(fname, PATH_MAX, (resolved_name == NULL ? buf : resolved_name), NULL); + if (res == 0) { + errno = GetLastError(); return NULL; + } + else if (res > PATH_MAX) { + errno = EINVAL; return NULL; + } + else if (resolved_name != NULL) { + return resolved_name; + } + else { + return mi_heap_strndup(heap, buf, PATH_MAX); + } +} +#else +#include // pathconf +static size_t mi_path_max() { + static size_t path_max = 0; + if (path_max <= 0) { + long m = pathconf("/",_PC_PATH_MAX); + if (m <= 0) path_max = 4096; // guess + else if (m < 256) path_max = 256; // at least 256 + else path_max = m; + } + return path_max; +} + +char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept { + if (resolved_name != NULL) { + return realpath(fname,resolved_name); + } + else { + size_t n = mi_path_max(); + char* buf = (char*)mi_malloc(n+1); + if (buf==NULL) return NULL; + char* rname = realpath(fname,buf); + char* result = mi_heap_strndup(heap,rname,n); // ok if `rname==NULL` + mi_free(buf); + return result; + } +} +#endif + +mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept { + return mi_heap_realpath(mi_get_default_heap(),fname,resolved_name); +} +#endif + +/*------------------------------------------------------- +C++ new and new_aligned +The standard requires calling into `get_new_handler` and +throwing the bad_alloc exception on failure. If we compile +with a C++ compiler we can implement this precisely. If we +use a C compiler we cannot throw a `bad_alloc` exception +but we call `exit` instead (i.e. not returning). +-------------------------------------------------------*/ + +#ifdef __cplusplus +#include +static bool mi_try_new_handler(bool nothrow) { + std::new_handler h = std::get_new_handler(); + if (h==NULL) { + if (!nothrow) throw std::bad_alloc(); + return false; + } + else { + h(); + return true; + } +} +#else +typedef void (*std_new_handler_t)(); + +#if (defined(__GNUC__) || defined(__clang__)) +std_new_handler_t __attribute((weak)) _ZSt15get_new_handlerv() { + return NULL; +} +std_new_handler_t mi_get_new_handler() { + return _ZSt15get_new_handlerv(); +} +#else +// note: on windows we could dynamically link to `?get_new_handler@std@@YAP6AXXZXZ`. +std_new_handler_t mi_get_new_handler() { + return NULL; +} +#endif + +static bool mi_try_new_handler(bool nothrow) { + std_new_handler_t h = mi_get_new_handler(); + if (h==NULL) { + if (!nothrow) exit(ENOMEM); // cannot throw in plain C, use exit as we are out of memory anyway. + return false; + } + else { + h(); + return true; + } +} +#endif + +static mi_decl_noinline void* mi_try_new(size_t size, bool nothrow ) { + void* p = NULL; + while(p == NULL && mi_try_new_handler(nothrow)) { + p = mi_malloc(size); + } + return p; +} + +mi_decl_restrict void* mi_new(size_t size) { + void* p = mi_malloc(size); + if (mi_unlikely(p == NULL)) return mi_try_new(size,false); + return p; +} + +mi_decl_restrict void* mi_new_nothrow(size_t size) mi_attr_noexcept { + void* p = mi_malloc(size); + if (mi_unlikely(p == NULL)) return mi_try_new(size, true); + return p; +} + +mi_decl_restrict void* mi_new_aligned(size_t size, size_t alignment) { + void* p; + do { + p = mi_malloc_aligned(size, alignment); + } + while(p == NULL && mi_try_new_handler(false)); + return p; +} + +mi_decl_restrict void* mi_new_aligned_nothrow(size_t size, size_t alignment) mi_attr_noexcept { + void* p; + do { + p = mi_malloc_aligned(size, alignment); + } + while(p == NULL && mi_try_new_handler(true)); + return p; +} + +mi_decl_restrict void* mi_new_n(size_t count, size_t size) { + size_t total; + if (mi_unlikely(mi_count_size_overflow(count, size, &total))) { + mi_try_new_handler(false); // on overflow we invoke the try_new_handler once to potentially throw std::bad_alloc + return NULL; + } + else { + return mi_new(total); + } +} + +void* mi_new_realloc(void* p, size_t newsize) { + void* q; + do { + q = mi_realloc(p, newsize); + } while (q == NULL && mi_try_new_handler(false)); + return q; +} + +void* mi_new_reallocn(void* p, size_t newcount, size_t size) { + size_t total; + if (mi_unlikely(mi_count_size_overflow(newcount, size, &total))) { + mi_try_new_handler(false); // on overflow we invoke the try_new_handler once to potentially throw std::bad_alloc + return NULL; + } + else { + return mi_new_realloc(p, total); + } +} diff --git a/extlib/mimalloc/src/arena.c b/extlib/mimalloc/src/arena.c new file mode 100644 index 0000000..bb9fc17 --- /dev/null +++ b/extlib/mimalloc/src/arena.c @@ -0,0 +1,357 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2019, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* ---------------------------------------------------------------------------- +"Arenas" are fixed area's of OS memory from which we can allocate +large blocks (>= MI_ARENA_BLOCK_SIZE, 32MiB). +In contrast to the rest of mimalloc, the arenas are shared between +threads and need to be accessed using atomic operations. + +Currently arenas are only used to for huge OS page (1GiB) reservations, +otherwise it delegates to direct allocation from the OS. +In the future, we can expose an API to manually add more kinds of arenas +which is sometimes needed for embedded devices or shared memory for example. +(We can also employ this with WASI or `sbrk` systems to reserve large arenas + on demand and be able to reuse them efficiently). + +The arena allocation needs to be thread safe and we use an atomic +bitmap to allocate. The current implementation of the bitmap can +only do this within a field (`uintptr_t`) so we can allocate at most +blocks of 2GiB (64*32MiB) and no object can cross the boundary. This +can lead to fragmentation but fortunately most objects will be regions +of 256MiB in practice. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc-internal.h" +#include "mimalloc-atomic.h" + +#include // memset + +#include "bitmap.inc.c" // atomic bitmap + + +// os.c +void* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool* large, mi_os_tld_t* tld); +void _mi_os_free_ex(void* p, size_t size, bool was_committed, mi_stats_t* stats); +void _mi_os_free(void* p, size_t size, mi_stats_t* stats); + +void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_secs, size_t* pages_reserved, size_t* psize); +void _mi_os_free_huge_pages(void* p, size_t size, mi_stats_t* stats); + +bool _mi_os_commit(void* p, size_t size, bool* is_zero, mi_stats_t* stats); + +/* ----------------------------------------------------------- + Arena allocation +----------------------------------------------------------- */ + +#define MI_SEGMENT_ALIGN MI_SEGMENT_SIZE +#define MI_ARENA_BLOCK_SIZE (8*MI_SEGMENT_ALIGN) // 32MiB +#define MI_ARENA_MAX_OBJ_SIZE (MI_BITMAP_FIELD_BITS * MI_ARENA_BLOCK_SIZE) // 2GiB +#define MI_ARENA_MIN_OBJ_SIZE (MI_ARENA_BLOCK_SIZE/2) // 16MiB +#define MI_MAX_ARENAS (64) // not more than 256 (since we use 8 bits in the memid) + +// A memory arena descriptor +typedef struct mi_arena_s { + _Atomic(uint8_t*) start; // the start of the memory area + size_t block_count; // size of the area in arena blocks (of `MI_ARENA_BLOCK_SIZE`) + size_t field_count; // number of bitmap fields (where `field_count * MI_BITMAP_FIELD_BITS >= block_count`) + int numa_node; // associated NUMA node + bool is_zero_init; // is the arena zero initialized? + bool is_committed; // is the memory committed + bool is_large; // large OS page allocated + volatile _Atomic(uintptr_t) search_idx; // optimization to start the search for free blocks + mi_bitmap_field_t* blocks_dirty; // are the blocks potentially non-zero? + mi_bitmap_field_t* blocks_committed; // if `!is_committed`, are the blocks committed? + mi_bitmap_field_t blocks_inuse[1]; // in-place bitmap of in-use blocks (of size `field_count`) +} mi_arena_t; + + +// The available arenas +static mi_decl_cache_align _Atomic(mi_arena_t*) mi_arenas[MI_MAX_ARENAS]; +static mi_decl_cache_align _Atomic(uintptr_t) mi_arena_count; // = 0 + + +/* ----------------------------------------------------------- + Arena allocations get a memory id where the lower 8 bits are + the arena index +1, and the upper bits the block index. +----------------------------------------------------------- */ + +// Use `0` as a special id for direct OS allocated memory. +#define MI_MEMID_OS 0 + +static size_t mi_arena_id_create(size_t arena_index, mi_bitmap_index_t bitmap_index) { + mi_assert_internal(arena_index < 0xFE); + mi_assert_internal(((bitmap_index << 8) >> 8) == bitmap_index); // no overflow? + return ((bitmap_index << 8) | ((arena_index+1) & 0xFF)); +} + +static void mi_arena_id_indices(size_t memid, size_t* arena_index, mi_bitmap_index_t* bitmap_index) { + mi_assert_internal(memid != MI_MEMID_OS); + *arena_index = (memid & 0xFF) - 1; + *bitmap_index = (memid >> 8); +} + +static size_t mi_block_count_of_size(size_t size) { + return _mi_divide_up(size, MI_ARENA_BLOCK_SIZE); +} + +/* ----------------------------------------------------------- + Thread safe allocation in an arena +----------------------------------------------------------- */ +static bool mi_arena_alloc(mi_arena_t* arena, size_t blocks, mi_bitmap_index_t* bitmap_idx) +{ + const size_t fcount = arena->field_count; + size_t idx = mi_atomic_read(&arena->search_idx); // start from last search + for (size_t visited = 0; visited < fcount; visited++, idx++) { + if (idx >= fcount) idx = 0; // wrap around + // try to atomically claim a range of bits + if (mi_bitmap_try_find_claim_field(arena->blocks_inuse, idx, blocks, bitmap_idx)) { + mi_atomic_write(&arena->search_idx, idx); // start search from here next time + return true; + } + } + return false; +} + + +/* ----------------------------------------------------------- + Arena Allocation +----------------------------------------------------------- */ + +static void* mi_arena_alloc_from(mi_arena_t* arena, size_t arena_index, size_t needed_bcount, + bool* commit, bool* large, bool* is_zero, size_t* memid, mi_os_tld_t* tld) +{ + mi_bitmap_index_t bitmap_index; + if (!mi_arena_alloc(arena, needed_bcount, &bitmap_index)) return NULL; + + // claimed it! set the dirty bits (todo: no need for an atomic op here?) + void* p = arena->start + (mi_bitmap_index_bit(bitmap_index)*MI_ARENA_BLOCK_SIZE); + *memid = mi_arena_id_create(arena_index, bitmap_index); + *is_zero = mi_bitmap_claim(arena->blocks_dirty, arena->field_count, needed_bcount, bitmap_index, NULL); + *large = arena->is_large; + if (arena->is_committed) { + // always committed + *commit = true; + } + else if (*commit) { + // arena not committed as a whole, but commit requested: ensure commit now + bool any_uncommitted; + mi_bitmap_claim(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index, &any_uncommitted); + if (any_uncommitted) { + bool commit_zero; + _mi_os_commit(p, needed_bcount * MI_ARENA_BLOCK_SIZE, &commit_zero, tld->stats); + if (commit_zero) *is_zero = true; + } + } + else { + // no need to commit, but check if already fully committed + *commit = mi_bitmap_is_claimed(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index); + } + return p; +} + +void* _mi_arena_alloc_aligned(size_t size, size_t alignment, + bool* commit, bool* large, bool* is_zero, + size_t* memid, mi_os_tld_t* tld) +{ + mi_assert_internal(commit != NULL && large != NULL && is_zero != NULL && memid != NULL && tld != NULL); + mi_assert_internal(size > 0); + *memid = MI_MEMID_OS; + *is_zero = false; + + // try to allocate in an arena if the alignment is small enough + // and the object is not too large or too small. + if (alignment <= MI_SEGMENT_ALIGN && + size <= MI_ARENA_MAX_OBJ_SIZE && + size >= MI_ARENA_MIN_OBJ_SIZE) + { + const size_t bcount = mi_block_count_of_size(size); + const int numa_node = _mi_os_numa_node(tld); // current numa node + + mi_assert_internal(size <= bcount*MI_ARENA_BLOCK_SIZE); + // try numa affine allocation + for (size_t i = 0; i < MI_MAX_ARENAS; i++) { + mi_arena_t* arena = mi_atomic_read_ptr_relaxed(mi_arena_t, &mi_arenas[i]); + if (arena==NULL) break; // end reached + if ((arena->numa_node<0 || arena->numa_node==numa_node) && // numa local? + (*large || !arena->is_large)) // large OS pages allowed, or arena is not large OS pages + { + void* p = mi_arena_alloc_from(arena, i, bcount, commit, large, is_zero, memid, tld); + mi_assert_internal((uintptr_t)p % alignment == 0); + if (p != NULL) return p; + } + } + // try from another numa node instead.. + for (size_t i = 0; i < MI_MAX_ARENAS; i++) { + mi_arena_t* arena = mi_atomic_read_ptr_relaxed(mi_arena_t, &mi_arenas[i]); + if (arena==NULL) break; // end reached + if ((arena->numa_node>=0 && arena->numa_node!=numa_node) && // not numa local! + (*large || !arena->is_large)) // large OS pages allowed, or arena is not large OS pages + { + void* p = mi_arena_alloc_from(arena, i, bcount, commit, large, is_zero, memid, tld); + mi_assert_internal((uintptr_t)p % alignment == 0); + if (p != NULL) return p; + } + } + } + + // finally, fall back to the OS + *is_zero = true; + *memid = MI_MEMID_OS; + return _mi_os_alloc_aligned(size, alignment, *commit, large, tld); +} + +void* _mi_arena_alloc(size_t size, bool* commit, bool* large, bool* is_zero, size_t* memid, mi_os_tld_t* tld) +{ + return _mi_arena_alloc_aligned(size, MI_ARENA_BLOCK_SIZE, commit, large, is_zero, memid, tld); +} + +/* ----------------------------------------------------------- + Arena free +----------------------------------------------------------- */ + +void _mi_arena_free(void* p, size_t size, size_t memid, bool all_committed, mi_stats_t* stats) { + mi_assert_internal(size > 0 && stats != NULL); + if (p==NULL) return; + if (size==0) return; + if (memid == MI_MEMID_OS) { + // was a direct OS allocation, pass through + _mi_os_free_ex(p, size, all_committed, stats); + } + else { + // allocated in an arena + size_t arena_idx; + size_t bitmap_idx; + mi_arena_id_indices(memid, &arena_idx, &bitmap_idx); + mi_assert_internal(arena_idx < MI_MAX_ARENAS); + mi_arena_t* arena = mi_atomic_read_ptr_relaxed(mi_arena_t,&mi_arenas[arena_idx]); + mi_assert_internal(arena != NULL); + if (arena == NULL) { + _mi_error_message(EINVAL, "trying to free from non-existent arena: %p, size %zu, memid: 0x%zx\n", p, size, memid); + return; + } + mi_assert_internal(arena->field_count > mi_bitmap_index_field(bitmap_idx)); + if (arena->field_count <= mi_bitmap_index_field(bitmap_idx)) { + _mi_error_message(EINVAL, "trying to free from non-existent arena block: %p, size %zu, memid: 0x%zx\n", p, size, memid); + return; + } + const size_t blocks = mi_block_count_of_size(size); + bool ones = mi_bitmap_unclaim(arena->blocks_inuse, arena->field_count, blocks, bitmap_idx); + if (!ones) { + _mi_error_message(EAGAIN, "trying to free an already freed block: %p, size %zu\n", p, size); + return; + }; + } +} + +/* ----------------------------------------------------------- + Add an arena. +----------------------------------------------------------- */ + +static bool mi_arena_add(mi_arena_t* arena) { + mi_assert_internal(arena != NULL); + mi_assert_internal((uintptr_t)mi_atomic_read_ptr_relaxed(uint8_t,&arena->start) % MI_SEGMENT_ALIGN == 0); + mi_assert_internal(arena->block_count > 0); + + uintptr_t i = mi_atomic_increment(&mi_arena_count); + if (i >= MI_MAX_ARENAS) { + mi_atomic_decrement(&mi_arena_count); + return false; + } + mi_atomic_write_ptr(mi_arena_t,&mi_arenas[i], arena); + return true; +} + + +/* ----------------------------------------------------------- + Reserve a huge page arena. +----------------------------------------------------------- */ +#include // ENOMEM + +// reserve at a specific numa node +int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs) mi_attr_noexcept { + if (pages==0) return 0; + if (numa_node < -1) numa_node = -1; + if (numa_node >= 0) numa_node = numa_node % _mi_os_numa_node_count(); + size_t hsize = 0; + size_t pages_reserved = 0; + void* p = _mi_os_alloc_huge_os_pages(pages, numa_node, timeout_msecs, &pages_reserved, &hsize); + if (p==NULL || pages_reserved==0) { + _mi_warning_message("failed to reserve %zu gb huge pages\n", pages); + return ENOMEM; + } + _mi_verbose_message("numa node %i: reserved %zu gb huge pages (of the %zu gb requested)\n", numa_node, pages_reserved, pages); + + size_t bcount = mi_block_count_of_size(hsize); + size_t fields = _mi_divide_up(bcount, MI_BITMAP_FIELD_BITS); + size_t asize = sizeof(mi_arena_t) + (2*fields*sizeof(mi_bitmap_field_t)); + mi_arena_t* arena = (mi_arena_t*)_mi_os_alloc(asize, &_mi_stats_main); // TODO: can we avoid allocating from the OS? + if (arena == NULL) { + _mi_os_free_huge_pages(p, hsize, &_mi_stats_main); + return ENOMEM; + } + arena->block_count = bcount; + arena->field_count = fields; + arena->start = (uint8_t*)p; + arena->numa_node = numa_node; // TODO: or get the current numa node if -1? (now it allows anyone to allocate on -1) + arena->is_large = true; + arena->is_zero_init = true; + arena->is_committed = true; + arena->search_idx = 0; + arena->blocks_dirty = &arena->blocks_inuse[fields]; // just after inuse bitmap + arena->blocks_committed = NULL; + // the bitmaps are already zero initialized due to os_alloc + // just claim leftover blocks if needed + ptrdiff_t post = (fields * MI_BITMAP_FIELD_BITS) - bcount; + mi_assert_internal(post >= 0); + if (post > 0) { + // don't use leftover bits at the end + mi_bitmap_index_t postidx = mi_bitmap_index_create(fields - 1, MI_BITMAP_FIELD_BITS - post); + mi_bitmap_claim(arena->blocks_inuse, fields, post, postidx, NULL); + } + + mi_arena_add(arena); + return 0; +} + + +// reserve huge pages evenly among the given number of numa nodes (or use the available ones as detected) +int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs) mi_attr_noexcept { + if (pages == 0) return 0; + + // pages per numa node + size_t numa_count = (numa_nodes > 0 ? numa_nodes : _mi_os_numa_node_count()); + if (numa_count <= 0) numa_count = 1; + const size_t pages_per = pages / numa_count; + const size_t pages_mod = pages % numa_count; + const size_t timeout_per = (timeout_msecs==0 ? 0 : (timeout_msecs / numa_count) + 50); + + // reserve evenly among numa nodes + for (size_t numa_node = 0; numa_node < numa_count && pages > 0; numa_node++) { + size_t node_pages = pages_per; // can be 0 + if (numa_node < pages_mod) node_pages++; + int err = mi_reserve_huge_os_pages_at(node_pages, (int)numa_node, timeout_per); + if (err) return err; + if (pages < node_pages) { + pages = 0; + } + else { + pages -= node_pages; + } + } + + return 0; +} + +int mi_reserve_huge_os_pages(size_t pages, double max_secs, size_t* pages_reserved) mi_attr_noexcept { + UNUSED(max_secs); + _mi_warning_message("mi_reserve_huge_os_pages is deprecated: use mi_reserve_huge_os_pages_interleave/at instead\n"); + if (pages_reserved != NULL) *pages_reserved = 0; + int err = mi_reserve_huge_os_pages_interleave(pages, 0, (size_t)(max_secs * 1000.0)); + if (err==0 && pages_reserved!=NULL) *pages_reserved = pages; + return err; +} diff --git a/extlib/mimalloc/src/bitmap.inc.c b/extlib/mimalloc/src/bitmap.inc.c new file mode 100644 index 0000000..c3813a4 --- /dev/null +++ b/extlib/mimalloc/src/bitmap.inc.c @@ -0,0 +1,240 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2019, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* ---------------------------------------------------------------------------- +This file is meant to be included in other files for efficiency. +It implements a bitmap that can set/reset sequences of bits atomically +and is used to concurrently claim memory ranges. + +A bitmap is an array of fields where each field is a machine word (`uintptr_t`) + +A current limitation is that the bit sequences cannot cross fields +and that the sequence must be smaller or equal to the bits in a field. +---------------------------------------------------------------------------- */ +#pragma once +#ifndef MI_BITMAP_C +#define MI_BITMAP_C + +#include "mimalloc.h" +#include "mimalloc-internal.h" + +/* ----------------------------------------------------------- + Bitmap definition +----------------------------------------------------------- */ + +#define MI_BITMAP_FIELD_BITS (8*MI_INTPTR_SIZE) +#define MI_BITMAP_FIELD_FULL (~((uintptr_t)0)) // all bits set + +// An atomic bitmap of `uintptr_t` fields +typedef volatile _Atomic(uintptr_t) mi_bitmap_field_t; +typedef mi_bitmap_field_t* mi_bitmap_t; + +// A bitmap index is the index of the bit in a bitmap. +typedef size_t mi_bitmap_index_t; + +// Create a bit index. +static inline mi_bitmap_index_t mi_bitmap_index_create(size_t idx, size_t bitidx) { + mi_assert_internal(bitidx < MI_BITMAP_FIELD_BITS); + return (idx*MI_BITMAP_FIELD_BITS) + bitidx; +} + +// Get the field index from a bit index. +static inline size_t mi_bitmap_index_field(mi_bitmap_index_t bitmap_idx) { + return (bitmap_idx / MI_BITMAP_FIELD_BITS); +} + +// Get the bit index in a bitmap field +static inline size_t mi_bitmap_index_bit_in_field(mi_bitmap_index_t bitmap_idx) { + return (bitmap_idx % MI_BITMAP_FIELD_BITS); +} + +// Get the full bit index +static inline size_t mi_bitmap_index_bit(mi_bitmap_index_t bitmap_idx) { + return bitmap_idx; +} + + +// The bit mask for a given number of blocks at a specified bit index. +static inline uintptr_t mi_bitmap_mask_(size_t count, size_t bitidx) { + mi_assert_internal(count + bitidx <= MI_BITMAP_FIELD_BITS); + if (count == MI_BITMAP_FIELD_BITS) return MI_BITMAP_FIELD_FULL; + return ((((uintptr_t)1 << count) - 1) << bitidx); +} + + +/* ----------------------------------------------------------- + Use bit scan forward/reverse to quickly find the first zero bit if it is available +----------------------------------------------------------- */ +#if defined(_MSC_VER) +#define MI_HAVE_BITSCAN +#include +static inline size_t mi_bsf(uintptr_t x) { + if (x==0) return 8*MI_INTPTR_SIZE; + DWORD idx; + MI_64(_BitScanForward)(&idx, x); + return idx; +} +static inline size_t mi_bsr(uintptr_t x) { + if (x==0) return 8*MI_INTPTR_SIZE; + DWORD idx; + MI_64(_BitScanReverse)(&idx, x); + return idx; +} +#elif defined(__GNUC__) || defined(__clang__) +#include // LONG_MAX +#define MI_HAVE_BITSCAN +#if (INTPTR_MAX == LONG_MAX) +# define MI_L(x) x##l +#else +# define MI_L(x) x##ll +#endif +static inline size_t mi_bsf(uintptr_t x) { + return (x==0 ? 8*MI_INTPTR_SIZE : MI_L(__builtin_ctz)(x)); +} +static inline size_t mi_bsr(uintptr_t x) { + return (x==0 ? 8*MI_INTPTR_SIZE : (8*MI_INTPTR_SIZE - 1) - MI_L(__builtin_clz)(x)); +} +#endif + +/* ----------------------------------------------------------- + Claim a bit sequence atomically +----------------------------------------------------------- */ + +// Try to atomically claim a sequence of `count` bits at in `idx` +// in the bitmap field. Returns `true` on success. +static inline bool mi_bitmap_try_claim_field(mi_bitmap_t bitmap, size_t bitmap_fields, const size_t count, mi_bitmap_index_t bitmap_idx) { + const size_t idx = mi_bitmap_index_field(bitmap_idx); + const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx); + const uintptr_t mask = mi_bitmap_mask_(count, bitidx); + mi_assert_internal(bitmap_fields > idx); UNUSED(bitmap_fields); + mi_assert_internal(bitidx + count <= MI_BITMAP_FIELD_BITS); + + uintptr_t field = mi_atomic_read_relaxed(&bitmap[idx]); + if ((field & mask) == 0) { // free? + if (mi_atomic_cas_strong(&bitmap[idx], (field|mask), field)) { + // claimed! + return true; + } + } + return false; +} + + +// Try to atomically claim a sequence of `count` bits in a single +// field at `idx` in `bitmap`. Returns `true` on success. +static inline bool mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, const size_t count, mi_bitmap_index_t* bitmap_idx) +{ + mi_assert_internal(bitmap_idx != NULL); + volatile _Atomic(uintptr_t)* field = &bitmap[idx]; + uintptr_t map = mi_atomic_read(field); + if (map==MI_BITMAP_FIELD_FULL) return false; // short cut + + // search for 0-bit sequence of length count + const uintptr_t mask = mi_bitmap_mask_(count, 0); + const size_t bitidx_max = MI_BITMAP_FIELD_BITS - count; + +#ifdef MI_HAVE_BITSCAN + size_t bitidx = mi_bsf(~map); // quickly find the first zero bit if possible +#else + size_t bitidx = 0; // otherwise start at 0 +#endif + uintptr_t m = (mask << bitidx); // invariant: m == mask shifted by bitidx + + // scan linearly for a free range of zero bits + while (bitidx <= bitidx_max) { + if ((map & m) == 0) { // are the mask bits free at bitidx? + mi_assert_internal((m >> bitidx) == mask); // no overflow? + const uintptr_t newmap = map | m; + mi_assert_internal((newmap^map) >> bitidx == mask); + if (!mi_atomic_cas_weak(field, newmap, map)) { // TODO: use strong cas here? + // no success, another thread claimed concurrently.. keep going + map = mi_atomic_read(field); + continue; + } + else { + // success, we claimed the bits! + *bitmap_idx = mi_bitmap_index_create(idx, bitidx); + return true; + } + } + else { + // on to the next bit range +#ifdef MI_HAVE_BITSCAN + const size_t shift = (count == 1 ? 1 : mi_bsr(map & m) - bitidx + 1); + mi_assert_internal(shift > 0 && shift <= count); +#else + const size_t shift = 1; +#endif + bitidx += shift; + m <<= shift; + } + } + // no bits found + return false; +} + + +// Find `count` bits of 0 and set them to 1 atomically; returns `true` on success. +// For now, `count` can be at most MI_BITMAP_FIELD_BITS and will never span fields. +static inline bool mi_bitmap_try_find_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t* bitmap_idx) { + for (size_t idx = 0; idx < bitmap_fields; idx++) { + if (mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx)) { + return true; + } + } + return false; +} + +// Set `count` bits at `bitmap_idx` to 0 atomically +// Returns `true` if all `count` bits were 1 previously. +static inline bool mi_bitmap_unclaim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) { + const size_t idx = mi_bitmap_index_field(bitmap_idx); + const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx); + const uintptr_t mask = mi_bitmap_mask_(count, bitidx); + mi_assert_internal(bitmap_fields > idx); UNUSED(bitmap_fields); + // mi_assert_internal((bitmap[idx] & mask) == mask); + uintptr_t prev = mi_atomic_and(&bitmap[idx], ~mask); + return ((prev & mask) == mask); +} + + +// Set `count` bits at `bitmap_idx` to 1 atomically +// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit. +static inline bool mi_bitmap_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* any_zero) { + const size_t idx = mi_bitmap_index_field(bitmap_idx); + const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx); + const uintptr_t mask = mi_bitmap_mask_(count, bitidx); + mi_assert_internal(bitmap_fields > idx); UNUSED(bitmap_fields); + //mi_assert_internal(any_zero != NULL || (bitmap[idx] & mask) == 0); + uintptr_t prev = mi_atomic_or(&bitmap[idx], mask); + if (any_zero != NULL) *any_zero = ((prev & mask) != mask); + return ((prev & mask) == 0); +} + +// Returns `true` if all `count` bits were 1. `any_ones` is `true` if there was at least one bit set to one. +static inline bool mi_bitmap_is_claimedx(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* any_ones) { + const size_t idx = mi_bitmap_index_field(bitmap_idx); + const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx); + const uintptr_t mask = mi_bitmap_mask_(count, bitidx); + mi_assert_internal(bitmap_fields > idx); UNUSED(bitmap_fields); + uintptr_t field = mi_atomic_read_relaxed(&bitmap[idx]); + if (any_ones != NULL) *any_ones = ((field & mask) != 0); + return ((field & mask) == mask); +} + +static inline bool mi_bitmap_is_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) { + return mi_bitmap_is_claimedx(bitmap, bitmap_fields, count, bitmap_idx, NULL); +} + +static inline bool mi_bitmap_is_any_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) { + bool any_ones; + mi_bitmap_is_claimedx(bitmap, bitmap_fields, count, bitmap_idx, &any_ones); + return any_ones; +} + + +#endif diff --git a/extlib/mimalloc/src/heap.c b/extlib/mimalloc/src/heap.c new file mode 100644 index 0000000..aab7e12 --- /dev/null +++ b/extlib/mimalloc/src/heap.c @@ -0,0 +1,555 @@ +/*---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +#include "mimalloc.h" +#include "mimalloc-internal.h" +#include "mimalloc-atomic.h" + +#include // memset, memcpy + + +/* ----------------------------------------------------------- + Helpers +----------------------------------------------------------- */ + +// return `true` if ok, `false` to break +typedef bool (heap_page_visitor_fun)(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2); + +// Visit all pages in a heap; returns `false` if break was called. +static bool mi_heap_visit_pages(mi_heap_t* heap, heap_page_visitor_fun* fn, void* arg1, void* arg2) +{ + if (heap==NULL || heap->page_count==0) return 0; + + // visit all pages + #if MI_DEBUG>1 + size_t total = heap->page_count; + #endif + size_t count = 0; + for (size_t i = 0; i <= MI_BIN_FULL; i++) { + mi_page_queue_t* pq = &heap->pages[i]; + mi_page_t* page = pq->first; + while(page != NULL) { + mi_page_t* next = page->next; // save next in case the page gets removed from the queue + mi_assert_internal(mi_page_heap(page) == heap); + count++; + if (!fn(heap, pq, page, arg1, arg2)) return false; + page = next; // and continue + } + } + mi_assert_internal(count == total); + return true; +} + + +#if MI_DEBUG>=2 +static bool mi_heap_page_is_valid(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) { + UNUSED(arg1); + UNUSED(arg2); + UNUSED(pq); + mi_assert_internal(mi_page_heap(page) == heap); + mi_segment_t* segment = _mi_page_segment(page); + mi_assert_internal(segment->thread_id == heap->thread_id); + mi_assert_expensive(_mi_page_is_valid(page)); + return true; +} +#endif +#if MI_DEBUG>=3 +static bool mi_heap_is_valid(mi_heap_t* heap) { + mi_assert_internal(heap!=NULL); + mi_heap_visit_pages(heap, &mi_heap_page_is_valid, NULL, NULL); + return true; +} +#endif + + + + +/* ----------------------------------------------------------- + "Collect" pages by migrating `local_free` and `thread_free` + lists and freeing empty pages. This is done when a thread + stops (and in that case abandons pages if there are still + blocks alive) +----------------------------------------------------------- */ + +typedef enum mi_collect_e { + MI_NORMAL, + MI_FORCE, + MI_ABANDON +} mi_collect_t; + + +static bool mi_heap_page_collect(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg_collect, void* arg2 ) { + UNUSED(arg2); + UNUSED(heap); + mi_assert_internal(mi_heap_page_is_valid(heap, pq, page, NULL, NULL)); + mi_collect_t collect = *((mi_collect_t*)arg_collect); + _mi_page_free_collect(page, collect >= MI_FORCE); + if (mi_page_all_free(page)) { + // no more used blocks, free the page. + // note: this will free retired pages as well. + _mi_page_free(page, pq, collect >= MI_FORCE); + } + else if (collect == MI_ABANDON) { + // still used blocks but the thread is done; abandon the page + _mi_page_abandon(page, pq); + } + return true; // don't break +} + +static bool mi_heap_page_never_delayed_free(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) { + UNUSED(arg1); + UNUSED(arg2); + UNUSED(heap); + UNUSED(pq); + _mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false); + return true; // don't break +} + +static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect) +{ + if (!mi_heap_is_initialized(heap)) return; + _mi_deferred_free(heap, collect >= MI_FORCE); + + // note: never reclaim on collect but leave it to threads that need storage to reclaim + if ( + #ifdef NDEBUG + collect == MI_FORCE + #else + collect >= MI_FORCE + #endif + && _mi_is_main_thread() && mi_heap_is_backing(heap) && !heap->no_reclaim) + { + // the main thread is abandoned (end-of-program), try to reclaim all abandoned segments. + // if all memory is freed by now, all segments should be freed. + _mi_abandoned_reclaim_all(heap, &heap->tld->segments); + } + + // if abandoning, mark all pages to no longer add to delayed_free + if (collect == MI_ABANDON) { + mi_heap_visit_pages(heap, &mi_heap_page_never_delayed_free, NULL, NULL); + } + + // free thread delayed blocks. + // (if abandoning, after this there are no more thread-delayed references into the pages.) + _mi_heap_delayed_free(heap); + + // collect retired pages + _mi_heap_collect_retired(heap, collect >= MI_FORCE); + + // collect all pages owned by this thread + mi_heap_visit_pages(heap, &mi_heap_page_collect, &collect, NULL); + mi_assert_internal( collect != MI_ABANDON || mi_atomic_read_ptr(mi_block_t,&heap->thread_delayed_free) == NULL ); + + // collect segment caches + if (collect >= MI_FORCE) { + _mi_segment_thread_collect(&heap->tld->segments); + } + + // collect regions on program-exit (or shared library unload) + if (collect >= MI_FORCE && _mi_is_main_thread() && mi_heap_is_backing(heap)) { + _mi_mem_collect(&heap->tld->os); + } +} + +void _mi_heap_collect_abandon(mi_heap_t* heap) { + mi_heap_collect_ex(heap, MI_ABANDON); +} + +void mi_heap_collect(mi_heap_t* heap, bool force) mi_attr_noexcept { + mi_heap_collect_ex(heap, (force ? MI_FORCE : MI_NORMAL)); +} + +void mi_collect(bool force) mi_attr_noexcept { + mi_heap_collect(mi_get_default_heap(), force); +} + + +/* ----------------------------------------------------------- + Heap new +----------------------------------------------------------- */ + +mi_heap_t* mi_heap_get_default(void) { + mi_thread_init(); + return mi_get_default_heap(); +} + +mi_heap_t* mi_heap_get_backing(void) { + mi_heap_t* heap = mi_heap_get_default(); + mi_assert_internal(heap!=NULL); + mi_heap_t* bheap = heap->tld->heap_backing; + mi_assert_internal(bheap!=NULL); + mi_assert_internal(bheap->thread_id == _mi_thread_id()); + return bheap; +} + +mi_heap_t* mi_heap_new(void) { + mi_heap_t* bheap = mi_heap_get_backing(); + mi_heap_t* heap = mi_heap_malloc_tp(bheap, mi_heap_t); // todo: OS allocate in secure mode? + if (heap==NULL) return NULL; + memcpy(heap, &_mi_heap_empty, sizeof(mi_heap_t)); + heap->tld = bheap->tld; + heap->thread_id = _mi_thread_id(); + _mi_random_split(&bheap->random, &heap->random); + heap->cookie = _mi_heap_random_next(heap) | 1; + heap->keys[0] = _mi_heap_random_next(heap); + heap->keys[1] = _mi_heap_random_next(heap); + heap->no_reclaim = true; // don't reclaim abandoned pages or otherwise destroy is unsafe + // push on the thread local heaps list + heap->next = heap->tld->heaps; + heap->tld->heaps = heap; + return heap; +} + +uintptr_t _mi_heap_random_next(mi_heap_t* heap) { + return _mi_random_next(&heap->random); +} + +// zero out the page queues +static void mi_heap_reset_pages(mi_heap_t* heap) { + mi_assert_internal(mi_heap_is_initialized(heap)); + // TODO: copy full empty heap instead? + memset(&heap->pages_free_direct, 0, sizeof(heap->pages_free_direct)); +#ifdef MI_MEDIUM_DIRECT + memset(&heap->pages_free_medium, 0, sizeof(heap->pages_free_medium)); +#endif + memcpy(&heap->pages, &_mi_heap_empty.pages, sizeof(heap->pages)); + heap->thread_delayed_free = NULL; + heap->page_count = 0; +} + +// called from `mi_heap_destroy` and `mi_heap_delete` to free the internal heap resources. +static void mi_heap_free(mi_heap_t* heap) { + mi_assert(heap != NULL); + mi_assert_internal(mi_heap_is_initialized(heap)); + if (mi_heap_is_backing(heap)) return; // dont free the backing heap + + // reset default + if (mi_heap_is_default(heap)) { + _mi_heap_set_default_direct(heap->tld->heap_backing); + } + + // remove ourselves from the thread local heaps list + // linear search but we expect the number of heaps to be relatively small + mi_heap_t* prev = NULL; + mi_heap_t* curr = heap->tld->heaps; + while (curr != heap && curr != NULL) { + prev = curr; + curr = curr->next; + } + mi_assert_internal(curr == heap); + if (curr == heap) { + if (prev != NULL) { prev->next = heap->next; } + else { heap->tld->heaps = heap->next; } + } + mi_assert_internal(heap->tld->heaps != NULL); + + // and free the used memory + mi_free(heap); +} + + +/* ----------------------------------------------------------- + Heap destroy +----------------------------------------------------------- */ + +static bool _mi_heap_page_destroy(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) { + UNUSED(arg1); + UNUSED(arg2); + UNUSED(heap); + UNUSED(pq); + + // ensure no more thread_delayed_free will be added + _mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false); + + // stats + const size_t bsize = mi_page_block_size(page); + if (bsize > MI_LARGE_OBJ_SIZE_MAX) { + if (bsize > MI_HUGE_OBJ_SIZE_MAX) { + _mi_stat_decrease(&heap->tld->stats.giant, bsize); + } + else { + _mi_stat_decrease(&heap->tld->stats.huge, bsize); + } + } +#if (MI_STAT>1) + _mi_page_free_collect(page, false); // update used count + const size_t inuse = page->used; + if (bsize <= MI_LARGE_OBJ_SIZE_MAX) { + mi_heap_stat_decrease(heap, normal[_mi_bin(bsize)], inuse); + } + mi_heap_stat_decrease(heap, malloc, bsize * inuse); // todo: off for aligned blocks... +#endif + + /// pretend it is all free now + mi_assert_internal(mi_page_thread_free(page) == NULL); + page->used = 0; + + // and free the page + // mi_page_free(page,false); + page->next = NULL; + page->prev = NULL; + _mi_segment_page_free(page,false /* no force? */, &heap->tld->segments); + + return true; // keep going +} + +void _mi_heap_destroy_pages(mi_heap_t* heap) { + mi_heap_visit_pages(heap, &_mi_heap_page_destroy, NULL, NULL); + mi_heap_reset_pages(heap); +} + +void mi_heap_destroy(mi_heap_t* heap) { + mi_assert(heap != NULL); + mi_assert(mi_heap_is_initialized(heap)); + mi_assert(heap->no_reclaim); + mi_assert_expensive(mi_heap_is_valid(heap)); + if (!mi_heap_is_initialized(heap)) return; + if (!heap->no_reclaim) { + // don't free in case it may contain reclaimed pages + mi_heap_delete(heap); + } + else { + // free all pages + _mi_heap_destroy_pages(heap); + mi_heap_free(heap); + } +} + + + +/* ----------------------------------------------------------- + Safe Heap delete +----------------------------------------------------------- */ + +// Tranfer the pages from one heap to the other +static void mi_heap_absorb(mi_heap_t* heap, mi_heap_t* from) { + mi_assert_internal(heap!=NULL); + if (from==NULL || from->page_count == 0) return; + + // reduce the size of the delayed frees + _mi_heap_delayed_free(from); + + // transfer all pages by appending the queues; this will set a new heap field + // so threads may do delayed frees in either heap for a while. + // note: appending waits for each page to not be in the `MI_DELAYED_FREEING` state + // so after this only the new heap will get delayed frees + for (size_t i = 0; i <= MI_BIN_FULL; i++) { + mi_page_queue_t* pq = &heap->pages[i]; + mi_page_queue_t* append = &from->pages[i]; + size_t pcount = _mi_page_queue_append(heap, pq, append); + heap->page_count += pcount; + from->page_count -= pcount; + } + mi_assert_internal(from->page_count == 0); + + // and do outstanding delayed frees in the `from` heap + // note: be careful here as the `heap` field in all those pages no longer point to `from`, + // turns out to be ok as `_mi_heap_delayed_free` only visits the list and calls a + // the regular `_mi_free_delayed_block` which is safe. + _mi_heap_delayed_free(from); + mi_assert_internal(from->thread_delayed_free == NULL); + + // and reset the `from` heap + mi_heap_reset_pages(from); +} + +// Safe delete a heap without freeing any still allocated blocks in that heap. +void mi_heap_delete(mi_heap_t* heap) +{ + mi_assert(heap != NULL); + mi_assert(mi_heap_is_initialized(heap)); + mi_assert_expensive(mi_heap_is_valid(heap)); + if (!mi_heap_is_initialized(heap)) return; + + if (!mi_heap_is_backing(heap)) { + // tranfer still used pages to the backing heap + mi_heap_absorb(heap->tld->heap_backing, heap); + } + else { + // the backing heap abandons its pages + _mi_heap_collect_abandon(heap); + } + mi_assert_internal(heap->page_count==0); + mi_heap_free(heap); +} + +mi_heap_t* mi_heap_set_default(mi_heap_t* heap) { + mi_assert(mi_heap_is_initialized(heap)); + if (!mi_heap_is_initialized(heap)) return NULL; + mi_assert_expensive(mi_heap_is_valid(heap)); + mi_heap_t* old = mi_get_default_heap(); + _mi_heap_set_default_direct(heap); + return old; +} + + + + +/* ----------------------------------------------------------- + Analysis +----------------------------------------------------------- */ + +// static since it is not thread safe to access heaps from other threads. +static mi_heap_t* mi_heap_of_block(const void* p) { + if (p == NULL) return NULL; + mi_segment_t* segment = _mi_ptr_segment(p); + bool valid = (_mi_ptr_cookie(segment) == segment->cookie); + mi_assert_internal(valid); + if (mi_unlikely(!valid)) return NULL; + return mi_page_heap(_mi_segment_page_of(segment,p)); +} + +bool mi_heap_contains_block(mi_heap_t* heap, const void* p) { + mi_assert(heap != NULL); + if (!mi_heap_is_initialized(heap)) return false; + return (heap == mi_heap_of_block(p)); +} + + +static bool mi_heap_page_check_owned(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* p, void* vfound) { + UNUSED(heap); + UNUSED(pq); + bool* found = (bool*)vfound; + mi_segment_t* segment = _mi_page_segment(page); + void* start = _mi_page_start(segment, page, NULL); + void* end = (uint8_t*)start + (page->capacity * mi_page_block_size(page)); + *found = (p >= start && p < end); + return (!*found); // continue if not found +} + +bool mi_heap_check_owned(mi_heap_t* heap, const void* p) { + mi_assert(heap != NULL); + if (!mi_heap_is_initialized(heap)) return false; + if (((uintptr_t)p & (MI_INTPTR_SIZE - 1)) != 0) return false; // only aligned pointers + bool found = false; + mi_heap_visit_pages(heap, &mi_heap_page_check_owned, (void*)p, &found); + return found; +} + +bool mi_check_owned(const void* p) { + return mi_heap_check_owned(mi_get_default_heap(), p); +} + +/* ----------------------------------------------------------- + Visit all heap blocks and areas + Todo: enable visiting abandoned pages, and + enable visiting all blocks of all heaps across threads +----------------------------------------------------------- */ + +// Separate struct to keep `mi_page_t` out of the public interface +typedef struct mi_heap_area_ex_s { + mi_heap_area_t area; + mi_page_t* page; +} mi_heap_area_ex_t; + +static bool mi_heap_area_visit_blocks(const mi_heap_area_ex_t* xarea, mi_block_visit_fun* visitor, void* arg) { + mi_assert(xarea != NULL); + if (xarea==NULL) return true; + const mi_heap_area_t* area = &xarea->area; + mi_page_t* page = xarea->page; + mi_assert(page != NULL); + if (page == NULL) return true; + + _mi_page_free_collect(page,true); + mi_assert_internal(page->local_free == NULL); + if (page->used == 0) return true; + + const size_t bsize = mi_page_block_size(page); + size_t psize; + uint8_t* pstart = _mi_page_start(_mi_page_segment(page), page, &psize); + + if (page->capacity == 1) { + // optimize page with one block + mi_assert_internal(page->used == 1 && page->free == NULL); + return visitor(mi_page_heap(page), area, pstart, bsize, arg); + } + + // create a bitmap of free blocks. + #define MI_MAX_BLOCKS (MI_SMALL_PAGE_SIZE / sizeof(void*)) + uintptr_t free_map[MI_MAX_BLOCKS / sizeof(uintptr_t)]; + memset(free_map, 0, sizeof(free_map)); + + size_t free_count = 0; + for (mi_block_t* block = page->free; block != NULL; block = mi_block_next(page,block)) { + free_count++; + mi_assert_internal((uint8_t*)block >= pstart && (uint8_t*)block < (pstart + psize)); + size_t offset = (uint8_t*)block - pstart; + mi_assert_internal(offset % bsize == 0); + size_t blockidx = offset / bsize; // Todo: avoid division? + mi_assert_internal( blockidx < MI_MAX_BLOCKS); + size_t bitidx = (blockidx / sizeof(uintptr_t)); + size_t bit = blockidx - (bitidx * sizeof(uintptr_t)); + free_map[bitidx] |= ((uintptr_t)1 << bit); + } + mi_assert_internal(page->capacity == (free_count + page->used)); + + // walk through all blocks skipping the free ones + size_t used_count = 0; + for (size_t i = 0; i < page->capacity; i++) { + size_t bitidx = (i / sizeof(uintptr_t)); + size_t bit = i - (bitidx * sizeof(uintptr_t)); + uintptr_t m = free_map[bitidx]; + if (bit == 0 && m == UINTPTR_MAX) { + i += (sizeof(uintptr_t) - 1); // skip a run of free blocks + } + else if ((m & ((uintptr_t)1 << bit)) == 0) { + used_count++; + uint8_t* block = pstart + (i * bsize); + if (!visitor(mi_page_heap(page), area, block, bsize, arg)) return false; + } + } + mi_assert_internal(page->used == used_count); + return true; +} + +typedef bool (mi_heap_area_visit_fun)(const mi_heap_t* heap, const mi_heap_area_ex_t* area, void* arg); + + +static bool mi_heap_visit_areas_page(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* vfun, void* arg) { + UNUSED(heap); + UNUSED(pq); + mi_heap_area_visit_fun* fun = (mi_heap_area_visit_fun*)vfun; + mi_heap_area_ex_t xarea; + const size_t bsize = mi_page_block_size(page); + xarea.page = page; + xarea.area.reserved = page->reserved * bsize; + xarea.area.committed = page->capacity * bsize; + xarea.area.blocks = _mi_page_start(_mi_page_segment(page), page, NULL); + xarea.area.used = page->used; + xarea.area.block_size = bsize; + return fun(heap, &xarea, arg); +} + +// Visit all heap pages as areas +static bool mi_heap_visit_areas(const mi_heap_t* heap, mi_heap_area_visit_fun* visitor, void* arg) { + if (visitor == NULL) return false; + return mi_heap_visit_pages((mi_heap_t*)heap, &mi_heap_visit_areas_page, (void*)(visitor), arg); // note: function pointer to void* :-{ +} + +// Just to pass arguments +typedef struct mi_visit_blocks_args_s { + bool visit_blocks; + mi_block_visit_fun* visitor; + void* arg; +} mi_visit_blocks_args_t; + +static bool mi_heap_area_visitor(const mi_heap_t* heap, const mi_heap_area_ex_t* xarea, void* arg) { + mi_visit_blocks_args_t* args = (mi_visit_blocks_args_t*)arg; + if (!args->visitor(heap, &xarea->area, NULL, xarea->area.block_size, args->arg)) return false; + if (args->visit_blocks) { + return mi_heap_area_visit_blocks(xarea, args->visitor, args->arg); + } + else { + return true; + } +} + +// Visit all blocks in a heap +bool mi_heap_visit_blocks(const mi_heap_t* heap, bool visit_blocks, mi_block_visit_fun* visitor, void* arg) { + mi_visit_blocks_args_t args = { visit_blocks, visitor, arg }; + return mi_heap_visit_areas(heap, &mi_heap_area_visitor, &args); +} diff --git a/extlib/mimalloc/src/init.c b/extlib/mimalloc/src/init.c new file mode 100644 index 0000000..1724bd2 --- /dev/null +++ b/extlib/mimalloc/src/init.c @@ -0,0 +1,572 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc-internal.h" + +#include // memcpy, memset +#include // atexit + +// Empty page used to initialize the small free pages array +const mi_page_t _mi_page_empty = { + 0, false, false, false, false, + 0, // capacity + 0, // reserved capacity + { 0 }, // flags + false, // is_zero + 0, // retire_expire + NULL, // free + #if MI_ENCODE_FREELIST + { 0, 0 }, + #endif + 0, // used + 0, // xblock_size + NULL, // local_free + ATOMIC_VAR_INIT(0), // xthread_free + ATOMIC_VAR_INIT(0), // xheap + NULL, NULL +}; + +#define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty) + +#if (MI_PADDING>0) && (MI_INTPTR_SIZE >= 8) +#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() } +#elif (MI_PADDING>0) +#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() } +#else +#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY() } +#endif + + +// Empty page queues for every bin +#define QNULL(sz) { NULL, NULL, (sz)*sizeof(uintptr_t) } +#define MI_PAGE_QUEUES_EMPTY \ + { QNULL(1), \ + QNULL( 1), QNULL( 2), QNULL( 3), QNULL( 4), QNULL( 5), QNULL( 6), QNULL( 7), QNULL( 8), /* 8 */ \ + QNULL( 10), QNULL( 12), QNULL( 14), QNULL( 16), QNULL( 20), QNULL( 24), QNULL( 28), QNULL( 32), /* 16 */ \ + QNULL( 40), QNULL( 48), QNULL( 56), QNULL( 64), QNULL( 80), QNULL( 96), QNULL( 112), QNULL( 128), /* 24 */ \ + QNULL( 160), QNULL( 192), QNULL( 224), QNULL( 256), QNULL( 320), QNULL( 384), QNULL( 448), QNULL( 512), /* 32 */ \ + QNULL( 640), QNULL( 768), QNULL( 896), QNULL( 1024), QNULL( 1280), QNULL( 1536), QNULL( 1792), QNULL( 2048), /* 40 */ \ + QNULL( 2560), QNULL( 3072), QNULL( 3584), QNULL( 4096), QNULL( 5120), QNULL( 6144), QNULL( 7168), QNULL( 8192), /* 48 */ \ + QNULL( 10240), QNULL( 12288), QNULL( 14336), QNULL( 16384), QNULL( 20480), QNULL( 24576), QNULL( 28672), QNULL( 32768), /* 56 */ \ + QNULL( 40960), QNULL( 49152), QNULL( 57344), QNULL( 65536), QNULL( 81920), QNULL( 98304), QNULL(114688), QNULL(131072), /* 64 */ \ + QNULL(163840), QNULL(196608), QNULL(229376), QNULL(262144), QNULL(327680), QNULL(393216), QNULL(458752), QNULL(524288), /* 72 */ \ + QNULL(MI_LARGE_OBJ_WSIZE_MAX + 1 /* 655360, Huge queue */), \ + QNULL(MI_LARGE_OBJ_WSIZE_MAX + 2) /* Full queue */ } + +#define MI_STAT_COUNT_NULL() {0,0,0,0} + +// Empty statistics +#if MI_STAT>1 +#define MI_STAT_COUNT_END_NULL() , { MI_STAT_COUNT_NULL(), MI_INIT32(MI_STAT_COUNT_NULL) } +#else +#define MI_STAT_COUNT_END_NULL() +#endif + +#define MI_STATS_NULL \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } \ + MI_STAT_COUNT_END_NULL() + +// -------------------------------------------------------- +// Statically allocate an empty heap as the initial +// thread local value for the default heap, +// and statically allocate the backing heap for the main +// thread so it can function without doing any allocation +// itself (as accessing a thread local for the first time +// may lead to allocation itself on some platforms) +// -------------------------------------------------------- + +const mi_heap_t _mi_heap_empty = { + NULL, + MI_SMALL_PAGES_EMPTY, + MI_PAGE_QUEUES_EMPTY, + ATOMIC_VAR_INIT(NULL), + 0, // tid + 0, // cookie + { 0, 0 }, // keys + { {0}, {0}, 0 }, + 0, // page count + MI_BIN_FULL, 0, // page retired min/max + NULL, // next + false +}; + +// the thread-local default heap for allocation +mi_decl_thread mi_heap_t* _mi_heap_default = (mi_heap_t*)&_mi_heap_empty; + +extern mi_heap_t _mi_heap_main; + +static mi_tld_t tld_main = { + 0, false, + &_mi_heap_main, &_mi_heap_main, + { { NULL, NULL }, {NULL ,NULL}, {NULL ,NULL, 0}, + 0, 0, 0, 0, 0, 0, NULL, + &tld_main.stats, &tld_main.os + }, // segments + { 0, &tld_main.stats }, // os + { MI_STATS_NULL } // stats +}; + +mi_heap_t _mi_heap_main = { + &tld_main, + MI_SMALL_PAGES_EMPTY, + MI_PAGE_QUEUES_EMPTY, + ATOMIC_VAR_INIT(NULL), + 0, // thread id + 0, // initial cookie + { 0, 0 }, // the key of the main heap can be fixed (unlike page keys that need to be secure!) + { {0x846ca68b}, {0}, 0 }, // random + 0, // page count + MI_BIN_FULL, 0, // page retired min/max + NULL, // next heap + false // can reclaim +}; + +bool _mi_process_is_initialized = false; // set to `true` in `mi_process_init`. + +mi_stats_t _mi_stats_main = { MI_STATS_NULL }; + + +static void mi_heap_main_init(void) { + if (_mi_heap_main.cookie == 0) { + _mi_heap_main.thread_id = _mi_thread_id(); + _mi_heap_main.cookie = _os_random_weak((uintptr_t)&mi_heap_main_init); + _mi_random_init(&_mi_heap_main.random); + _mi_heap_main.keys[0] = _mi_heap_random_next(&_mi_heap_main); + _mi_heap_main.keys[1] = _mi_heap_random_next(&_mi_heap_main); + } +} + +mi_heap_t* _mi_heap_main_get(void) { + mi_heap_main_init(); + return &_mi_heap_main; +} + + +/* ----------------------------------------------------------- + Initialization and freeing of the thread local heaps +----------------------------------------------------------- */ + +// note: in x64 in release build `sizeof(mi_thread_data_t)` is under 4KiB (= OS page size). +typedef struct mi_thread_data_s { + mi_heap_t heap; // must come first due to cast in `_mi_heap_done` + mi_tld_t tld; +} mi_thread_data_t; + +// Initialize the thread local default heap, called from `mi_thread_init` +static bool _mi_heap_init(void) { + if (mi_heap_is_initialized(mi_get_default_heap())) return true; + if (_mi_is_main_thread()) { + // mi_assert_internal(_mi_heap_main.thread_id != 0); // can happen on freeBSD where alloc is called before any initialization + // the main heap is statically allocated + mi_heap_main_init(); + _mi_heap_set_default_direct(&_mi_heap_main); + //mi_assert_internal(_mi_heap_default->tld->heap_backing == mi_get_default_heap()); + } + else { + // use `_mi_os_alloc` to allocate directly from the OS + mi_thread_data_t* td = (mi_thread_data_t*)_mi_os_alloc(sizeof(mi_thread_data_t), &_mi_stats_main); // Todo: more efficient allocation? + if (td == NULL) { + // if this fails, try once more. (issue #257) + td = (mi_thread_data_t*)_mi_os_alloc(sizeof(mi_thread_data_t), &_mi_stats_main); + if (td == NULL) { + // really out of memory + _mi_error_message(ENOMEM, "unable to allocate thread local heap metadata (%zu bytes)\n", sizeof(mi_thread_data_t)); + return false; + } + } + // OS allocated so already zero initialized + mi_tld_t* tld = &td->tld; + mi_heap_t* heap = &td->heap; + memcpy(heap, &_mi_heap_empty, sizeof(*heap)); + heap->thread_id = _mi_thread_id(); + _mi_random_init(&heap->random); + heap->cookie = _mi_heap_random_next(heap) | 1; + heap->keys[0] = _mi_heap_random_next(heap); + heap->keys[1] = _mi_heap_random_next(heap); + heap->tld = tld; + tld->heap_backing = heap; + tld->heaps = heap; + tld->segments.stats = &tld->stats; + tld->segments.os = &tld->os; + tld->os.stats = &tld->stats; + _mi_heap_set_default_direct(heap); + } + return false; +} + +// Free the thread local default heap (called from `mi_thread_done`) +static bool _mi_heap_done(mi_heap_t* heap) { + if (!mi_heap_is_initialized(heap)) return true; + + // reset default heap + _mi_heap_set_default_direct(_mi_is_main_thread() ? &_mi_heap_main : (mi_heap_t*)&_mi_heap_empty); + + // switch to backing heap + heap = heap->tld->heap_backing; + if (!mi_heap_is_initialized(heap)) return false; + + // delete all non-backing heaps in this thread + mi_heap_t* curr = heap->tld->heaps; + while (curr != NULL) { + mi_heap_t* next = curr->next; // save `next` as `curr` will be freed + if (curr != heap) { + mi_assert_internal(!mi_heap_is_backing(curr)); + mi_heap_delete(curr); + } + curr = next; + } + mi_assert_internal(heap->tld->heaps == heap && heap->next == NULL); + mi_assert_internal(mi_heap_is_backing(heap)); + + // collect if not the main thread + if (heap != &_mi_heap_main) { + _mi_heap_collect_abandon(heap); + } + + + // merge stats + _mi_stats_done(&heap->tld->stats); + + // free if not the main thread + if (heap != &_mi_heap_main) { + mi_assert_internal(heap->tld->segments.count == 0 || heap->thread_id != _mi_thread_id()); + _mi_os_free(heap, sizeof(mi_thread_data_t), &_mi_stats_main); + } +#if 0 + // never free the main thread even in debug mode; if a dll is linked statically with mimalloc, + // there may still be delete/free calls after the mi_fls_done is called. Issue #207 + else { + _mi_heap_destroy_pages(heap); + mi_assert_internal(heap->tld->heap_backing == &_mi_heap_main); + } +#endif + return false; +} + + + +// -------------------------------------------------------- +// Try to run `mi_thread_done()` automatically so any memory +// owned by the thread but not yet released can be abandoned +// and re-owned by another thread. +// +// 1. windows dynamic library: +// call from DllMain on DLL_THREAD_DETACH +// 2. windows static library: +// use `FlsAlloc` to call a destructor when the thread is done +// 3. unix, pthreads: +// use a pthread key to call a destructor when a pthread is done +// +// In the last two cases we also need to call `mi_process_init` +// to set up the thread local keys. +// -------------------------------------------------------- + +static void _mi_thread_done(mi_heap_t* default_heap); + +#ifdef __wasi__ +// no pthreads in the WebAssembly Standard Interface +#elif !defined(_WIN32) +#define MI_USE_PTHREADS +#endif + +#if defined(_WIN32) && defined(MI_SHARED_LIB) + // nothing to do as it is done in DllMain +#elif defined(_WIN32) && !defined(MI_SHARED_LIB) + // use thread local storage keys to detect thread ending + #include + #include + #if (_WIN32_WINNT < 0x600) // before Windows Vista + WINBASEAPI DWORD WINAPI FlsAlloc( _In_opt_ PFLS_CALLBACK_FUNCTION lpCallback ); + WINBASEAPI PVOID WINAPI FlsGetValue( _In_ DWORD dwFlsIndex ); + WINBASEAPI BOOL WINAPI FlsSetValue( _In_ DWORD dwFlsIndex, _In_opt_ PVOID lpFlsData ); + WINBASEAPI BOOL WINAPI FlsFree(_In_ DWORD dwFlsIndex); + #endif + static DWORD mi_fls_key = (DWORD)(-1); + static void NTAPI mi_fls_done(PVOID value) { + if (value!=NULL) _mi_thread_done((mi_heap_t*)value); + } +#elif defined(MI_USE_PTHREADS) + // use pthread local storage keys to detect thread ending + // (and used with MI_TLS_PTHREADS for the default heap) + #include + pthread_key_t _mi_heap_default_key = (pthread_key_t)(-1); + static void mi_pthread_done(void* value) { + if (value!=NULL) _mi_thread_done((mi_heap_t*)value); + } +#elif defined(__wasi__) +// no pthreads in the WebAssembly Standard Interface +#else + #pragma message("define a way to call mi_thread_done when a thread is done") +#endif + +// Set up handlers so `mi_thread_done` is called automatically +static void mi_process_setup_auto_thread_done(void) { + static bool tls_initialized = false; // fine if it races + if (tls_initialized) return; + tls_initialized = true; + #if defined(_WIN32) && defined(MI_SHARED_LIB) + // nothing to do as it is done in DllMain + #elif defined(_WIN32) && !defined(MI_SHARED_LIB) + mi_fls_key = FlsAlloc(&mi_fls_done); + #elif defined(MI_USE_PTHREADS) + mi_assert_internal(_mi_heap_default_key == (pthread_key_t)(-1)); + pthread_key_create(&_mi_heap_default_key, &mi_pthread_done); + #endif + _mi_heap_set_default_direct(&_mi_heap_main); +} + + +bool _mi_is_main_thread(void) { + return (_mi_heap_main.thread_id==0 || _mi_heap_main.thread_id == _mi_thread_id()); +} + +// This is called from the `mi_malloc_generic` +void mi_thread_init(void) mi_attr_noexcept +{ + // ensure our process has started already + mi_process_init(); + + // initialize the thread local default heap + // (this will call `_mi_heap_set_default_direct` and thus set the + // fiber/pthread key to a non-zero value, ensuring `_mi_thread_done` is called) + if (_mi_heap_init()) return; // returns true if already initialized + + // don't further initialize for the main thread + if (_mi_is_main_thread()) return; + + mi_heap_t* const heap = mi_get_default_heap(); + if (mi_heap_is_initialized(heap)) { _mi_stat_increase(&heap->tld->stats.threads, 1); } + + //_mi_verbose_message("thread init: 0x%zx\n", _mi_thread_id()); +} + +void mi_thread_done(void) mi_attr_noexcept { + _mi_thread_done(mi_get_default_heap()); +} + +static void _mi_thread_done(mi_heap_t* heap) { + // check thread-id as on Windows shutdown with FLS the main (exit) thread may call this on thread-local heaps... + if (heap->thread_id != _mi_thread_id()) return; + + // stats + if (!_mi_is_main_thread() && mi_heap_is_initialized(heap)) { + _mi_stat_decrease(&heap->tld->stats.threads, 1); + } + + // abandon the thread local heap + if (_mi_heap_done(heap)) return; // returns true if already ran +} + +void _mi_heap_set_default_direct(mi_heap_t* heap) { + mi_assert_internal(heap != NULL); + #if defined(MI_TLS_SLOT) + mi_tls_slot_set(MI_TLS_SLOT,heap); + #elif defined(MI_TLS_PTHREAD_SLOT_OFS) + *mi_tls_pthread_heap_slot() = heap; + #elif defined(MI_TLS_PTHREAD) + // we use _mi_heap_default_key + #else + _mi_heap_default = heap; + #endif + + // ensure the default heap is passed to `_mi_thread_done` + // setting to a non-NULL value also ensures `mi_thread_done` is called. + #if defined(_WIN32) && defined(MI_SHARED_LIB) + // nothing to do as it is done in DllMain + #elif defined(_WIN32) && !defined(MI_SHARED_LIB) + mi_assert_internal(mi_fls_key != 0); + FlsSetValue(mi_fls_key, heap); + #elif defined(MI_USE_PTHREADS) + if (_mi_heap_default_key != (pthread_key_t)(-1)) { // can happen during recursive invocation on freeBSD + pthread_setspecific(_mi_heap_default_key, heap); + } + #endif +} + + +// -------------------------------------------------------- +// Run functions on process init/done, and thread init/done +// -------------------------------------------------------- +static void mi_process_done(void); + +static bool os_preloading = true; // true until this module is initialized +static bool mi_redirected = false; // true if malloc redirects to mi_malloc + +// Returns true if this module has not been initialized; Don't use C runtime routines until it returns false. +bool _mi_preloading() { + return os_preloading; +} + +bool mi_is_redirected() mi_attr_noexcept { + return mi_redirected; +} + +// Communicate with the redirection module on Windows +#if defined(_WIN32) && defined(MI_SHARED_LIB) +#ifdef __cplusplus +extern "C" { +#endif +mi_decl_export void _mi_redirect_entry(DWORD reason) { + // called on redirection; careful as this may be called before DllMain + if (reason == DLL_PROCESS_ATTACH) { + mi_redirected = true; + } + else if (reason == DLL_PROCESS_DETACH) { + mi_redirected = false; + } + else if (reason == DLL_THREAD_DETACH) { + mi_thread_done(); + } +} +__declspec(dllimport) bool mi_allocator_init(const char** message); +__declspec(dllimport) void mi_allocator_done(); +#ifdef __cplusplus +} +#endif +#else +static bool mi_allocator_init(const char** message) { + if (message != NULL) *message = NULL; + return true; +} +static void mi_allocator_done() { + // nothing to do +} +#endif + +// Called once by the process loader +static void mi_process_load(void) { + mi_heap_main_init(); + #if defined(MI_TLS_RECURSE_GUARD) + volatile mi_heap_t* dummy = _mi_heap_default; // access TLS to allocate it before setting tls_initialized to true; + UNUSED(dummy); + #endif + os_preloading = false; + atexit(&mi_process_done); + _mi_options_init(); + mi_process_init(); + //mi_stats_reset();- + if (mi_redirected) _mi_verbose_message("malloc is redirected.\n"); + + // show message from the redirector (if present) + const char* msg = NULL; + mi_allocator_init(&msg); + if (msg != NULL && (mi_option_is_enabled(mi_option_verbose) || mi_option_is_enabled(mi_option_show_errors))) { + _mi_fputs(NULL,NULL,NULL,msg); + } +} + +// Initialize the process; called by thread_init or the process loader +void mi_process_init(void) mi_attr_noexcept { + // ensure we are called once + if (_mi_process_is_initialized) return; + _mi_process_is_initialized = true; + mi_process_setup_auto_thread_done(); + + _mi_verbose_message("process init: 0x%zx\n", _mi_thread_id()); + _mi_os_init(); + mi_heap_main_init(); + #if (MI_DEBUG) + _mi_verbose_message("debug level : %d\n", MI_DEBUG); + #endif + _mi_verbose_message("secure level: %d\n", MI_SECURE); + mi_thread_init(); + mi_stats_reset(); // only call stat reset *after* thread init (or the heap tld == NULL) + + if (mi_option_is_enabled(mi_option_reserve_huge_os_pages)) { + size_t pages = mi_option_get(mi_option_reserve_huge_os_pages); + mi_reserve_huge_os_pages_interleave(pages, 0, pages*500); + } +} + +// Called when the process is done (through `at_exit`) +static void mi_process_done(void) { + // only shutdown if we were initialized + if (!_mi_process_is_initialized) return; + // ensure we are called once + static bool process_done = false; + if (process_done) return; + process_done = true; + + #if defined(_WIN32) && !defined(MI_SHARED_LIB) + FlsSetValue(mi_fls_key, NULL); // don't call main-thread callback + FlsFree(mi_fls_key); // call thread-done on all threads to prevent dangling callback pointer if statically linked with a DLL; Issue #208 + #endif + + #if (MI_DEBUG != 0) || !defined(MI_SHARED_LIB) + // free all memory if possible on process exit. This is not needed for a stand-alone process + // but should be done if mimalloc is statically linked into another shared library which + // is repeatedly loaded/unloaded, see issue #281. + mi_collect(true /* force */ ); + #endif + + if (mi_option_is_enabled(mi_option_show_stats) || mi_option_is_enabled(mi_option_verbose)) { + mi_stats_print(NULL); + } + mi_allocator_done(); + _mi_verbose_message("process done: 0x%zx\n", _mi_heap_main.thread_id); + os_preloading = true; // don't call the C runtime anymore +} + + + +#if defined(_WIN32) && defined(MI_SHARED_LIB) + // Windows DLL: easy to hook into process_init and thread_done + __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) { + UNUSED(reserved); + UNUSED(inst); + if (reason==DLL_PROCESS_ATTACH) { + mi_process_load(); + } + else if (reason==DLL_THREAD_DETACH) { + if (!mi_is_redirected()) mi_thread_done(); + } + return TRUE; + } + +#elif defined(__cplusplus) + // C++: use static initialization to detect process start + static bool _mi_process_init(void) { + mi_process_load(); + return (_mi_heap_main.thread_id != 0); + } + static bool mi_initialized = _mi_process_init(); + +#elif defined(__GNUC__) || defined(__clang__) + // GCC,Clang: use the constructor attribute + static void __attribute__((constructor)) _mi_process_init(void) { + mi_process_load(); + } + +#elif defined(_MSC_VER) + // MSVC: use data section magic for static libraries + // See + static int _mi_process_init(void) { + mi_process_load(); + return 0; + } + typedef int(*_crt_cb)(void); + #ifdef _M_X64 + __pragma(comment(linker, "/include:" "_mi_msvc_initu")) + #pragma section(".CRT$XIU", long, read) + #else + __pragma(comment(linker, "/include:" "__mi_msvc_initu")) + #endif + #pragma data_seg(".CRT$XIU") + _crt_cb _mi_msvc_initu[] = { &_mi_process_init }; + #pragma data_seg() + +#else +#pragma message("define a way to call mi_process_load on your platform") +#endif diff --git a/extlib/mimalloc/src/options.c b/extlib/mimalloc/src/options.c new file mode 100644 index 0000000..f29b387 --- /dev/null +++ b/extlib/mimalloc/src/options.c @@ -0,0 +1,518 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc-internal.h" +#include "mimalloc-atomic.h" + +#include +#include // strtol +#include // strncpy, strncat, strlen, strstr +#include // toupper +#include + +static uintptr_t mi_max_error_count = 16; // stop outputting errors after this + +static void mi_add_stderr_output(); + +int mi_version(void) mi_attr_noexcept { + return MI_MALLOC_VERSION; +} + +#ifdef _WIN32 +#include +#endif + +// -------------------------------------------------------- +// Options +// These can be accessed by multiple threads and may be +// concurrently initialized, but an initializing data race +// is ok since they resolve to the same value. +// -------------------------------------------------------- +typedef enum mi_init_e { + UNINIT, // not yet initialized + DEFAULTED, // not found in the environment, use default value + INITIALIZED // found in environment or set explicitly +} mi_init_t; + +typedef struct mi_option_desc_s { + long value; // the value + mi_init_t init; // is it initialized yet? (from the environment) + mi_option_t option; // for debugging: the option index should match the option + const char* name; // option name without `mimalloc_` prefix +} mi_option_desc_t; + +#define MI_OPTION(opt) mi_option_##opt, #opt +#define MI_OPTION_DESC(opt) {0, UNINIT, MI_OPTION(opt) } + +static mi_option_desc_t options[_mi_option_last] = +{ + // stable options +#if MI_DEBUG || defined(MI_SHOW_ERRORS) + { 1, UNINIT, MI_OPTION(show_errors) }, +#else + { 0, UNINIT, MI_OPTION(show_errors) }, +#endif + { 0, UNINIT, MI_OPTION(show_stats) }, + { 0, UNINIT, MI_OPTION(verbose) }, + + // the following options are experimental and not all combinations make sense. + { 1, UNINIT, MI_OPTION(eager_commit) }, // commit on demand + #if defined(_WIN32) || (MI_INTPTR_SIZE <= 4) // and other OS's without overcommit? + { 0, UNINIT, MI_OPTION(eager_region_commit) }, + { 1, UNINIT, MI_OPTION(reset_decommits) }, // reset decommits memory + #else + { 1, UNINIT, MI_OPTION(eager_region_commit) }, + { 0, UNINIT, MI_OPTION(reset_decommits) }, // reset uses MADV_FREE/MADV_DONTNEED + #endif + { 0, UNINIT, MI_OPTION(large_os_pages) }, // use large OS pages, use only with eager commit to prevent fragmentation of VMA's + { 0, UNINIT, MI_OPTION(reserve_huge_os_pages) }, + { 0, UNINIT, MI_OPTION(segment_cache) }, // cache N segments per thread + { 1, UNINIT, MI_OPTION(page_reset) }, // reset page memory on free + { 0, UNINIT, MI_OPTION(abandoned_page_reset) },// reset free page memory when a thread terminates + { 0, UNINIT, MI_OPTION(segment_reset) }, // reset segment memory on free (needs eager commit) +#if defined(__NetBSD__) + { 0, UNINIT, MI_OPTION(eager_commit_delay) }, // the first N segments per thread are not eagerly committed +#else + { 1, UNINIT, MI_OPTION(eager_commit_delay) }, // the first N segments per thread are not eagerly committed +#endif + { 100, UNINIT, MI_OPTION(reset_delay) }, // reset delay in milli-seconds + { 0, UNINIT, MI_OPTION(use_numa_nodes) }, // 0 = use available numa nodes, otherwise use at most N nodes. + { 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose + { 16, UNINIT, MI_OPTION(max_errors) } // maximum errors that are output +}; + +static void mi_option_init(mi_option_desc_t* desc); + +void _mi_options_init(void) { + // called on process load; should not be called before the CRT is initialized! + // (e.g. do not call this from process_init as that may run before CRT initialization) + mi_add_stderr_output(); // now it safe to use stderr for output + for(int i = 0; i < _mi_option_last; i++ ) { + mi_option_t option = (mi_option_t)i; + long l = mi_option_get(option); UNUSED(l); // initialize + if (option != mi_option_verbose) { + mi_option_desc_t* desc = &options[option]; + _mi_verbose_message("option '%s': %ld\n", desc->name, desc->value); + } + } + mi_max_error_count = mi_option_get(mi_option_max_errors); +} + +long mi_option_get(mi_option_t option) { + mi_assert(option >= 0 && option < _mi_option_last); + mi_option_desc_t* desc = &options[option]; + mi_assert(desc->option == option); // index should match the option + if (mi_unlikely(desc->init == UNINIT)) { + mi_option_init(desc); + } + return desc->value; +} + +void mi_option_set(mi_option_t option, long value) { + mi_assert(option >= 0 && option < _mi_option_last); + mi_option_desc_t* desc = &options[option]; + mi_assert(desc->option == option); // index should match the option + desc->value = value; + desc->init = INITIALIZED; +} + +void mi_option_set_default(mi_option_t option, long value) { + mi_assert(option >= 0 && option < _mi_option_last); + mi_option_desc_t* desc = &options[option]; + if (desc->init != INITIALIZED) { + desc->value = value; + } +} + +bool mi_option_is_enabled(mi_option_t option) { + return (mi_option_get(option) != 0); +} + +void mi_option_set_enabled(mi_option_t option, bool enable) { + mi_option_set(option, (enable ? 1 : 0)); +} + +void mi_option_set_enabled_default(mi_option_t option, bool enable) { + mi_option_set_default(option, (enable ? 1 : 0)); +} + +void mi_option_enable(mi_option_t option) { + mi_option_set_enabled(option,true); +} + +void mi_option_disable(mi_option_t option) { + mi_option_set_enabled(option,false); +} + + +static void mi_out_stderr(const char* msg, void* arg) { + UNUSED(arg); + #ifdef _WIN32 + // on windows with redirection, the C runtime cannot handle locale dependent output + // after the main thread closes so we use direct console output. + if (!_mi_preloading()) { _cputs(msg); } + #else + fputs(msg, stderr); + #endif +} + +// Since an output function can be registered earliest in the `main` +// function we also buffer output that happens earlier. When +// an output function is registered it is called immediately with +// the output up to that point. +#ifndef MI_MAX_DELAY_OUTPUT +#define MI_MAX_DELAY_OUTPUT (32*1024) +#endif +static char out_buf[MI_MAX_DELAY_OUTPUT+1]; +static _Atomic(uintptr_t) out_len; + +static void mi_out_buf(const char* msg, void* arg) { + UNUSED(arg); + if (msg==NULL) return; + if (mi_atomic_read_relaxed(&out_len)>=MI_MAX_DELAY_OUTPUT) return; + size_t n = strlen(msg); + if (n==0) return; + // claim space + uintptr_t start = mi_atomic_add(&out_len, n); + if (start >= MI_MAX_DELAY_OUTPUT) return; + // check bound + if (start+n >= MI_MAX_DELAY_OUTPUT) { + n = MI_MAX_DELAY_OUTPUT-start-1; + } + memcpy(&out_buf[start], msg, n); +} + +static void mi_out_buf_flush(mi_output_fun* out, bool no_more_buf, void* arg) { + if (out==NULL) return; + // claim (if `no_more_buf == true`, no more output will be added after this point) + size_t count = mi_atomic_add(&out_len, (no_more_buf ? MI_MAX_DELAY_OUTPUT : 1)); + // and output the current contents + if (count>MI_MAX_DELAY_OUTPUT) count = MI_MAX_DELAY_OUTPUT; + out_buf[count] = 0; + out(out_buf,arg); + if (!no_more_buf) { + out_buf[count] = '\n'; // if continue with the buffer, insert a newline + } +} + + +// Once this module is loaded, switch to this routine +// which outputs to stderr and the delayed output buffer. +static void mi_out_buf_stderr(const char* msg, void* arg) { + mi_out_stderr(msg,arg); + mi_out_buf(msg,arg); +} + + + +// -------------------------------------------------------- +// Default output handler +// -------------------------------------------------------- + +// Should be atomic but gives errors on many platforms as generally we cannot cast a function pointer to a uintptr_t. +// For now, don't register output from multiple threads. +#pragma warning(suppress:4180) +static mi_output_fun* volatile mi_out_default; // = NULL +static volatile _Atomic(void*) mi_out_arg; // = NULL + +static mi_output_fun* mi_out_get_default(void** parg) { + if (parg != NULL) { *parg = mi_atomic_read_ptr(void,&mi_out_arg); } + mi_output_fun* out = mi_out_default; + return (out == NULL ? &mi_out_buf : out); +} + +void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept { + mi_out_default = (out == NULL ? &mi_out_stderr : out); // stop using the delayed output buffer + mi_atomic_write_ptr(void,&mi_out_arg, arg); + if (out!=NULL) mi_out_buf_flush(out,true,arg); // output all the delayed output now +} + +// add stderr to the delayed output after the module is loaded +static void mi_add_stderr_output() { + mi_assert_internal(mi_out_default == NULL); + mi_out_buf_flush(&mi_out_stderr, false, NULL); // flush current contents to stderr + mi_out_default = &mi_out_buf_stderr; // and add stderr to the delayed output +} + +// -------------------------------------------------------- +// Messages, all end up calling `_mi_fputs`. +// -------------------------------------------------------- +static volatile _Atomic(uintptr_t) error_count; // = 0; // when MAX_ERROR_COUNT stop emitting errors and warnings + +// When overriding malloc, we may recurse into mi_vfprintf if an allocation +// inside the C runtime causes another message. +static mi_decl_thread bool recurse = false; + +static bool mi_recurse_enter(void) { + #ifdef MI_TLS_RECURSE_GUARD + if (_mi_preloading()) return true; + #endif + if (recurse) return false; + recurse = true; + return true; +} + +static void mi_recurse_exit(void) { + #ifdef MI_TLS_RECURSE_GUARD + if (_mi_preloading()) return; + #endif + recurse = false; +} + +void _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* message) { + if (out==NULL || (FILE*)out==stdout || (FILE*)out==stderr) { // TODO: use mi_out_stderr for stderr? + if (!mi_recurse_enter()) return; + out = mi_out_get_default(&arg); + if (prefix != NULL) out(prefix, arg); + out(message, arg); + mi_recurse_exit(); + } + else { + if (prefix != NULL) out(prefix, arg); + out(message, arg); + } +} + +// Define our own limited `fprintf` that avoids memory allocation. +// We do this using `snprintf` with a limited buffer. +static void mi_vfprintf( mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args ) { + char buf[512]; + if (fmt==NULL) return; + if (!mi_recurse_enter()) return; + vsnprintf(buf,sizeof(buf)-1,fmt,args); + mi_recurse_exit(); + _mi_fputs(out,arg,prefix,buf); +} + +void _mi_fprintf( mi_output_fun* out, void* arg, const char* fmt, ... ) { + va_list args; + va_start(args,fmt); + mi_vfprintf(out,arg,NULL,fmt,args); + va_end(args); +} + +void _mi_trace_message(const char* fmt, ...) { + if (mi_option_get(mi_option_verbose) <= 1) return; // only with verbose level 2 or higher + va_list args; + va_start(args, fmt); + mi_vfprintf(NULL, NULL, "mimalloc: ", fmt, args); + va_end(args); +} + +void _mi_verbose_message(const char* fmt, ...) { + if (!mi_option_is_enabled(mi_option_verbose)) return; + va_list args; + va_start(args,fmt); + mi_vfprintf(NULL, NULL, "mimalloc: ", fmt, args); + va_end(args); +} + +static void mi_show_error_message(const char* fmt, va_list args) { + if (!mi_option_is_enabled(mi_option_show_errors) && !mi_option_is_enabled(mi_option_verbose)) return; + if (mi_atomic_increment(&error_count) > mi_max_error_count) return; + mi_vfprintf(NULL, NULL, "mimalloc: error: ", fmt, args); +} + +void _mi_warning_message(const char* fmt, ...) { + if (!mi_option_is_enabled(mi_option_show_errors) && !mi_option_is_enabled(mi_option_verbose)) return; + if (mi_atomic_increment(&error_count) > mi_max_error_count) return; + va_list args; + va_start(args,fmt); + mi_vfprintf(NULL, NULL, "mimalloc: warning: ", fmt, args); + va_end(args); +} + + +#if MI_DEBUG +void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) { + _mi_fprintf(NULL, NULL, "mimalloc: assertion failed: at \"%s\":%u, %s\n assertion: \"%s\"\n", fname, line, (func==NULL?"":func), assertion); + abort(); +} +#endif + +// -------------------------------------------------------- +// Errors +// -------------------------------------------------------- + +static mi_error_fun* volatile mi_error_handler; // = NULL +static volatile _Atomic(void*) mi_error_arg; // = NULL + +static void mi_error_default(int err) { + UNUSED(err); +#if (MI_DEBUG>0) + if (err==EFAULT) { + #ifdef _MSC_VER + __debugbreak(); + #endif + abort(); + } +#endif +#if (MI_SECURE>0) + if (err==EFAULT) { // abort on serious errors in secure mode (corrupted meta-data) + abort(); + } +#endif +#if defined(MI_XMALLOC) + if (err==ENOMEM || err==EOVERFLOW) { // abort on memory allocation fails in xmalloc mode + abort(); + } +#endif +} + +void mi_register_error(mi_error_fun* fun, void* arg) { + mi_error_handler = fun; // can be NULL + mi_atomic_write_ptr(void,&mi_error_arg, arg); +} + +void _mi_error_message(int err, const char* fmt, ...) { + // show detailed error message + va_list args; + va_start(args, fmt); + mi_show_error_message(fmt, args); + va_end(args); + // and call the error handler which may abort (or return normally) + if (mi_error_handler != NULL) { + mi_error_handler(err, mi_atomic_read_ptr(void,&mi_error_arg)); + } + else { + mi_error_default(err); + } +} + +// -------------------------------------------------------- +// Initialize options by checking the environment +// -------------------------------------------------------- + +static void mi_strlcpy(char* dest, const char* src, size_t dest_size) { + dest[0] = 0; + #pragma warning(suppress:4996) + strncpy(dest, src, dest_size - 1); + dest[dest_size - 1] = 0; +} + +static void mi_strlcat(char* dest, const char* src, size_t dest_size) { + #pragma warning(suppress:4996) + strncat(dest, src, dest_size - 1); + dest[dest_size - 1] = 0; +} + +static inline int mi_strnicmp(const char* s, const char* t, size_t n) { + if (n==0) return 0; + for (; *s != 0 && *t != 0 && n > 0; s++, t++, n--) { + if (toupper(*s) != toupper(*t)) break; + } + return (n==0 ? 0 : *s - *t); +} + +#if defined _WIN32 +// On Windows use GetEnvironmentVariable instead of getenv to work +// reliably even when this is invoked before the C runtime is initialized. +// i.e. when `_mi_preloading() == true`. +// Note: on windows, environment names are not case sensitive. +#include +static bool mi_getenv(const char* name, char* result, size_t result_size) { + result[0] = 0; + size_t len = GetEnvironmentVariableA(name, result, (DWORD)result_size); + return (len > 0 && len < result_size); +} +#elif !defined(MI_USE_ENVIRON) || (MI_USE_ENVIRON!=0) +// On Posix systemsr use `environ` to acces environment variables +// even before the C runtime is initialized. +#if defined(__APPLE__) +#include +static char** mi_get_environ(void) { + return (*_NSGetEnviron()); +} +#else +extern char** environ; +static char** mi_get_environ(void) { + return environ; +} +#endif +static bool mi_getenv(const char* name, char* result, size_t result_size) { + if (name==NULL) return false; + const size_t len = strlen(name); + if (len == 0) return false; + char** env = mi_get_environ(); + if (env == NULL) return false; + // compare up to 256 entries + for (int i = 0; i < 256 && env[i] != NULL; i++) { + const char* s = env[i]; + if (mi_strnicmp(name, s, len) == 0 && s[len] == '=') { // case insensitive + // found it + mi_strlcpy(result, s + len + 1, result_size); + return true; + } + } + return false; +} +#else +// fallback: use standard C `getenv` but this cannot be used while initializing the C runtime +static bool mi_getenv(const char* name, char* result, size_t result_size) { + // cannot call getenv() when still initializing the C runtime. + if (_mi_preloading()) return false; + const char* s = getenv(name); + if (s == NULL) { + // we check the upper case name too. + char buf[64+1]; + size_t len = strlen(name); + if (len >= sizeof(buf)) len = sizeof(buf) - 1; + for (size_t i = 0; i < len; i++) { + buf[i] = toupper(name[i]); + } + buf[len] = 0; + s = getenv(buf); + } + if (s != NULL && strlen(s) < result_size) { + mi_strlcpy(result, s, result_size); + return true; + } + else { + return false; + } +} +#endif + +static void mi_option_init(mi_option_desc_t* desc) { + // Read option value from the environment + char buf[64+1]; + mi_strlcpy(buf, "mimalloc_", sizeof(buf)); + mi_strlcat(buf, desc->name, sizeof(buf)); + char s[64+1]; + if (mi_getenv(buf, s, sizeof(s))) { + size_t len = strlen(s); + if (len >= sizeof(buf)) len = sizeof(buf) - 1; + for (size_t i = 0; i < len; i++) { + buf[i] = (char)toupper(s[i]); + } + buf[len] = 0; + if (buf[0]==0 || strstr("1;TRUE;YES;ON", buf) != NULL) { + desc->value = 1; + desc->init = INITIALIZED; + } + else if (strstr("0;FALSE;NO;OFF", buf) != NULL) { + desc->value = 0; + desc->init = INITIALIZED; + } + else { + char* end = buf; + long value = strtol(buf, &end, 10); + if (*end == 0) { + desc->value = value; + desc->init = INITIALIZED; + } + else { + _mi_warning_message("environment option mimalloc_%s has an invalid value: %s\n", desc->name, buf); + desc->init = DEFAULTED; + } + } + mi_assert_internal(desc->init != UNINIT); + } + else if (!_mi_preloading()) { + desc->init = DEFAULTED; + } +} diff --git a/extlib/mimalloc/src/os.c b/extlib/mimalloc/src/os.c new file mode 100644 index 0000000..fe30cf4 --- /dev/null +++ b/extlib/mimalloc/src/os.c @@ -0,0 +1,1176 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE // ensure mmap flags are defined +#endif + +#if defined(__sun) +// illumos provides new mman.h api when any of these are defined +// otherwise the old api based on caddr_t which predates the void pointers one. +// stock solaris provides only the former, chose to atomically to discard those +// flags only here rather than project wide tough. +#undef _XOPEN_SOURCE +#undef _POSIX_C_SOURCE +#endif +#include "mimalloc.h" +#include "mimalloc-internal.h" +#include "mimalloc-atomic.h" + +#include // strerror + + +#if defined(_WIN32) +#include +#elif defined(__wasi__) +// stdlib.h is all we need, and has already been included in mimalloc.h +#else +#include // mmap +#include // sysconf +#if defined(__linux__) +#include +#if defined(__GLIBC__) +#include // linux mmap flags +#else +#include +#endif +#endif +#if defined(__APPLE__) +#include +#if !TARGET_IOS_IPHONE && !TARGET_IOS_SIMULATOR +#include +#endif +#endif +#if defined(__HAIKU__) +#define madvise posix_madvise +#define MADV_DONTNEED POSIX_MADV_DONTNEED +#endif +#endif + +/* ----------------------------------------------------------- + Initialization. + On windows initializes support for aligned allocation and + large OS pages (if MIMALLOC_LARGE_OS_PAGES is true). +----------------------------------------------------------- */ +bool _mi_os_decommit(void* addr, size_t size, mi_stats_t* stats); + +static void* mi_align_up_ptr(void* p, size_t alignment) { + return (void*)_mi_align_up((uintptr_t)p, alignment); +} + +static uintptr_t _mi_align_down(uintptr_t sz, size_t alignment) { + return (sz / alignment) * alignment; +} + +static void* mi_align_down_ptr(void* p, size_t alignment) { + return (void*)_mi_align_down((uintptr_t)p, alignment); +} + +// page size (initialized properly in `os_init`) +static size_t os_page_size = 4096; + +// minimal allocation granularity +static size_t os_alloc_granularity = 4096; + +// if non-zero, use large page allocation +static size_t large_os_page_size = 0; + +// OS (small) page size +size_t _mi_os_page_size() { + return os_page_size; +} + +// if large OS pages are supported (2 or 4MiB), then return the size, otherwise return the small page size (4KiB) +size_t _mi_os_large_page_size() { + return (large_os_page_size != 0 ? large_os_page_size : _mi_os_page_size()); +} + +static bool use_large_os_page(size_t size, size_t alignment) { + // if we have access, check the size and alignment requirements + if (large_os_page_size == 0 || !mi_option_is_enabled(mi_option_large_os_pages)) return false; + return ((size % large_os_page_size) == 0 && (alignment % large_os_page_size) == 0); +} + +// round to a good OS allocation size (bounded by max 12.5% waste) +size_t _mi_os_good_alloc_size(size_t size) { + size_t align_size; + if (size < 512*KiB) align_size = _mi_os_page_size(); + else if (size < 2*MiB) align_size = 64*KiB; + else if (size < 8*MiB) align_size = 256*KiB; + else if (size < 32*MiB) align_size = 1*MiB; + else align_size = 4*MiB; + if (size >= (SIZE_MAX - align_size)) return size; // possible overflow? + return _mi_align_up(size, align_size); +} + +#if defined(_WIN32) +// We use VirtualAlloc2 for aligned allocation, but it is only supported on Windows 10 and Windows Server 2016. +// So, we need to look it up dynamically to run on older systems. (use __stdcall for 32-bit compatibility) +// NtAllocateVirtualAllocEx is used for huge OS page allocation (1GiB) +// +// We hide MEM_EXTENDED_PARAMETER to compile with older SDK's. +#include +typedef PVOID (__stdcall *PVirtualAlloc2)(HANDLE, PVOID, SIZE_T, ULONG, ULONG, /* MEM_EXTENDED_PARAMETER* */ void*, ULONG); +typedef NTSTATUS (__stdcall *PNtAllocateVirtualMemoryEx)(HANDLE, PVOID*, SIZE_T*, ULONG, ULONG, /* MEM_EXTENDED_PARAMETER* */ PVOID, ULONG); +static PVirtualAlloc2 pVirtualAlloc2 = NULL; +static PNtAllocateVirtualMemoryEx pNtAllocateVirtualMemoryEx = NULL; + +// Similarly, GetNumaProcesorNodeEx is only supported since Windows 7 +#if (_WIN32_WINNT < 0x601) // before Win7 +typedef struct _PROCESSOR_NUMBER { WORD Group; BYTE Number; BYTE Reserved; } PROCESSOR_NUMBER, *PPROCESSOR_NUMBER; +#endif +typedef VOID (__stdcall *PGetCurrentProcessorNumberEx)(PPROCESSOR_NUMBER ProcNumber); +typedef BOOL (__stdcall *PGetNumaProcessorNodeEx)(PPROCESSOR_NUMBER Processor, PUSHORT NodeNumber); +typedef BOOL (__stdcall* PGetNumaNodeProcessorMaskEx)(USHORT Node, PGROUP_AFFINITY ProcessorMask); +static PGetCurrentProcessorNumberEx pGetCurrentProcessorNumberEx = NULL; +static PGetNumaProcessorNodeEx pGetNumaProcessorNodeEx = NULL; +static PGetNumaNodeProcessorMaskEx pGetNumaNodeProcessorMaskEx = NULL; + +static bool mi_win_enable_large_os_pages() +{ + if (large_os_page_size > 0) return true; + + // Try to see if large OS pages are supported + // To use large pages on Windows, we first need access permission + // Set "Lock pages in memory" permission in the group policy editor + // + unsigned long err = 0; + HANDLE token = NULL; + BOOL ok = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token); + if (ok) { + TOKEN_PRIVILEGES tp; + ok = LookupPrivilegeValue(NULL, TEXT("SeLockMemoryPrivilege"), &tp.Privileges[0].Luid); + if (ok) { + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + ok = AdjustTokenPrivileges(token, FALSE, &tp, 0, (PTOKEN_PRIVILEGES)NULL, 0); + if (ok) { + err = GetLastError(); + ok = (err == ERROR_SUCCESS); + if (ok) { + large_os_page_size = GetLargePageMinimum(); + } + } + } + CloseHandle(token); + } + if (!ok) { + if (err == 0) err = GetLastError(); + _mi_warning_message("cannot enable large OS page support, error %lu\n", err); + } + return (ok!=0); +} + +void _mi_os_init(void) { + // get the page size + SYSTEM_INFO si; + GetSystemInfo(&si); + if (si.dwPageSize > 0) os_page_size = si.dwPageSize; + if (si.dwAllocationGranularity > 0) os_alloc_granularity = si.dwAllocationGranularity; + // get the VirtualAlloc2 function + HINSTANCE hDll; + hDll = LoadLibrary(TEXT("kernelbase.dll")); + if (hDll != NULL) { + // use VirtualAlloc2FromApp if possible as it is available to Windows store apps + pVirtualAlloc2 = (PVirtualAlloc2)(void (*)(void))GetProcAddress(hDll, "VirtualAlloc2FromApp"); + if (pVirtualAlloc2==NULL) pVirtualAlloc2 = (PVirtualAlloc2)(void (*)(void))GetProcAddress(hDll, "VirtualAlloc2"); + FreeLibrary(hDll); + } + // NtAllocateVirtualMemoryEx is used for huge page allocation + hDll = LoadLibrary(TEXT("ntdll.dll")); + if (hDll != NULL) { + pNtAllocateVirtualMemoryEx = (PNtAllocateVirtualMemoryEx)(void (*)(void))GetProcAddress(hDll, "NtAllocateVirtualMemoryEx"); + FreeLibrary(hDll); + } + // Try to use Win7+ numa API + hDll = LoadLibrary(TEXT("kernel32.dll")); + if (hDll != NULL) { + pGetCurrentProcessorNumberEx = (PGetCurrentProcessorNumberEx)(void (*)(void))GetProcAddress(hDll, "GetCurrentProcessorNumberEx"); + pGetNumaProcessorNodeEx = (PGetNumaProcessorNodeEx)(void (*)(void))GetProcAddress(hDll, "GetNumaProcessorNodeEx"); + pGetNumaNodeProcessorMaskEx = (PGetNumaNodeProcessorMaskEx)(void (*)(void))GetProcAddress(hDll, "GetNumaNodeProcessorMaskEx"); + FreeLibrary(hDll); + } + if (mi_option_is_enabled(mi_option_large_os_pages) || mi_option_is_enabled(mi_option_reserve_huge_os_pages)) { + mi_win_enable_large_os_pages(); + } +} +#elif defined(__wasi__) +void _mi_os_init() { + os_page_size = 0x10000; // WebAssembly has a fixed page size: 64KB + os_alloc_granularity = 16; +} +#else +void _mi_os_init() { + // get the page size + long result = sysconf(_SC_PAGESIZE); + if (result > 0) { + os_page_size = (size_t)result; + os_alloc_granularity = os_page_size; + } + large_os_page_size = 2*MiB; // TODO: can we query the OS for this? +} +#endif + + +/* ----------------------------------------------------------- + Raw allocation on Windows (VirtualAlloc) and Unix's (mmap). +----------------------------------------------------------- */ + +static bool mi_os_mem_free(void* addr, size_t size, bool was_committed, mi_stats_t* stats) +{ + if (addr == NULL || size == 0) return true; // || _mi_os_is_huge_reserved(addr) + bool err = false; +#if defined(_WIN32) + err = (VirtualFree(addr, 0, MEM_RELEASE) == 0); +#elif defined(__wasi__) + err = 0; // WebAssembly's heap cannot be shrunk +#else + err = (munmap(addr, size) == -1); +#endif + if (was_committed) _mi_stat_decrease(&stats->committed, size); + _mi_stat_decrease(&stats->reserved, size); + if (err) { + #pragma warning(suppress:4996) + _mi_warning_message("munmap failed: %s, addr 0x%8li, size %lu\n", strerror(errno), (size_t)addr, size); + return false; + } + else { + return true; + } +} + +static void* mi_os_get_aligned_hint(size_t try_alignment, size_t size); + +#ifdef _WIN32 +static void* mi_win_virtual_allocx(void* addr, size_t size, size_t try_alignment, DWORD flags) { +#if (MI_INTPTR_SIZE >= 8) + // on 64-bit systems, try to use the virtual address area after 4TiB for 4MiB aligned allocations + void* hint; + if (addr == NULL && (hint = mi_os_get_aligned_hint(try_alignment,size)) != NULL) { + void* p = VirtualAlloc(hint, size, flags, PAGE_READWRITE); + if (p != NULL) return p; + DWORD err = GetLastError(); + if (err != ERROR_INVALID_ADDRESS && // If linked with multiple instances, we may have tried to allocate at an already allocated area (#210) + err != ERROR_INVALID_PARAMETER) { // Windows7 instability (#230) + return NULL; + } + // fall through + } +#endif +#if defined(MEM_EXTENDED_PARAMETER_TYPE_BITS) + // on modern Windows try use VirtualAlloc2 for aligned allocation + if (try_alignment > 0 && (try_alignment % _mi_os_page_size()) == 0 && pVirtualAlloc2 != NULL) { + MEM_ADDRESS_REQUIREMENTS reqs = { 0, 0, 0 }; + reqs.Alignment = try_alignment; + MEM_EXTENDED_PARAMETER param = { {0, 0}, {0} }; + param.Type = MemExtendedParameterAddressRequirements; + param.Pointer = &reqs; + return (*pVirtualAlloc2)(GetCurrentProcess(), addr, size, flags, PAGE_READWRITE, ¶m, 1); + } +#endif + // last resort + return VirtualAlloc(addr, size, flags, PAGE_READWRITE); +} + +static void* mi_win_virtual_alloc(void* addr, size_t size, size_t try_alignment, DWORD flags, bool large_only, bool allow_large, bool* is_large) { + mi_assert_internal(!(large_only && !allow_large)); + static volatile _Atomic(uintptr_t) large_page_try_ok; // = 0; + void* p = NULL; + if ((large_only || use_large_os_page(size, try_alignment)) + && allow_large && (flags&MEM_COMMIT)!=0 && (flags&MEM_RESERVE)!=0) { + uintptr_t try_ok = mi_atomic_read(&large_page_try_ok); + if (!large_only && try_ok > 0) { + // if a large page allocation fails, it seems the calls to VirtualAlloc get very expensive. + // therefore, once a large page allocation failed, we don't try again for `large_page_try_ok` times. + mi_atomic_cas_weak(&large_page_try_ok, try_ok - 1, try_ok); + } + else { + // large OS pages must always reserve and commit. + *is_large = true; + p = mi_win_virtual_allocx(addr, size, try_alignment, flags | MEM_LARGE_PAGES); + if (large_only) return p; + // fall back to non-large page allocation on error (`p == NULL`). + if (p == NULL) { + mi_atomic_write(&large_page_try_ok,10); // on error, don't try again for the next N allocations + } + } + } + if (p == NULL) { + *is_large = ((flags&MEM_LARGE_PAGES) != 0); + p = mi_win_virtual_allocx(addr, size, try_alignment, flags); + } + if (p == NULL) { + _mi_warning_message("unable to allocate OS memory (%zu bytes, error code: %i, address: %p, large only: %d, allow large: %d)\n", size, GetLastError(), addr, large_only, allow_large); + } + return p; +} + +#elif defined(__wasi__) +static void* mi_wasm_heap_grow(size_t size, size_t try_alignment) { + uintptr_t base = __builtin_wasm_memory_size(0) * _mi_os_page_size(); + uintptr_t aligned_base = _mi_align_up(base, (uintptr_t) try_alignment); + size_t alloc_size = _mi_align_up( aligned_base - base + size, _mi_os_page_size()); + mi_assert(alloc_size >= size && (alloc_size % _mi_os_page_size()) == 0); + if (alloc_size < size) return NULL; + if (__builtin_wasm_memory_grow(0, alloc_size / _mi_os_page_size()) == SIZE_MAX) { + errno = ENOMEM; + return NULL; + } + return (void*)aligned_base; +} +#else +#define MI_OS_USE_MMAP +static void* mi_unix_mmapx(void* addr, size_t size, size_t try_alignment, int protect_flags, int flags, int fd) { + void* p = NULL; + #if (MI_INTPTR_SIZE >= 8) && !defined(MAP_ALIGNED) + // on 64-bit systems, use the virtual address area after 4TiB for 4MiB aligned allocations + void* hint; + if (addr == NULL && (hint = mi_os_get_aligned_hint(try_alignment, size)) != NULL) { + p = mmap(hint,size,protect_flags,flags,fd,0); + if (p==MAP_FAILED) p = NULL; // fall back to regular mmap + } + #else + UNUSED(try_alignment); + UNUSED(mi_os_get_aligned_hint); + #endif + if (p==NULL) { + p = mmap(addr,size,protect_flags,flags,fd,0); + if (p==MAP_FAILED) p = NULL; + } + return p; +} + +static void* mi_unix_mmap(void* addr, size_t size, size_t try_alignment, int protect_flags, bool large_only, bool allow_large, bool* is_large) { + void* p = NULL; + #if !defined(MAP_ANONYMOUS) + #define MAP_ANONYMOUS MAP_ANON + #endif + #if !defined(MAP_NORESERVE) + #define MAP_NORESERVE 0 + #endif + int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE; + int fd = -1; + #if defined(MAP_ALIGNED) // BSD + if (try_alignment > 0) { + size_t n = _mi_bsr(try_alignment); + if (((size_t)1 << n) == try_alignment && n >= 12 && n <= 30) { // alignment is a power of 2 and 4096 <= alignment <= 1GiB + flags |= MAP_ALIGNED(n); + } + } + #endif + #if defined(PROT_MAX) + protect_flags |= PROT_MAX(PROT_READ | PROT_WRITE); // BSD + #endif + #if defined(VM_MAKE_TAG) + // macOS: tracking anonymous page with a specific ID. (All up to 98 are taken officially but LLVM sanitizers had taken 99) + int os_tag = (int)mi_option_get(mi_option_os_tag); + if (os_tag < 100 || os_tag > 255) os_tag = 100; + fd = VM_MAKE_TAG(os_tag); + #endif + if ((large_only || use_large_os_page(size, try_alignment)) && allow_large) { + static volatile _Atomic(uintptr_t) large_page_try_ok; // = 0; + uintptr_t try_ok = mi_atomic_read(&large_page_try_ok); + if (!large_only && try_ok > 0) { + // If the OS is not configured for large OS pages, or the user does not have + // enough permission, the `mmap` will always fail (but it might also fail for other reasons). + // Therefore, once a large page allocation failed, we don't try again for `large_page_try_ok` times + // to avoid too many failing calls to mmap. + mi_atomic_cas_weak(&large_page_try_ok, try_ok - 1, try_ok); + } + else { + int lflags = flags & ~MAP_NORESERVE; // using NORESERVE on huge pages seems to fail on Linux + int lfd = fd; + #ifdef MAP_ALIGNED_SUPER + lflags |= MAP_ALIGNED_SUPER; + #endif + #ifdef MAP_HUGETLB + lflags |= MAP_HUGETLB; + #endif + #ifdef MAP_HUGE_1GB + static bool mi_huge_pages_available = true; + if ((size % GiB) == 0 && mi_huge_pages_available) { + lflags |= MAP_HUGE_1GB; + } + else + #endif + { + #ifdef MAP_HUGE_2MB + lflags |= MAP_HUGE_2MB; + #endif + } + #ifdef VM_FLAGS_SUPERPAGE_SIZE_2MB + lfd |= VM_FLAGS_SUPERPAGE_SIZE_2MB; + #endif + if (large_only || lflags != flags) { + // try large OS page allocation + *is_large = true; + p = mi_unix_mmapx(addr, size, try_alignment, protect_flags, lflags, lfd); + #ifdef MAP_HUGE_1GB + if (p == NULL && (lflags & MAP_HUGE_1GB) != 0) { + mi_huge_pages_available = false; // don't try huge 1GiB pages again + _mi_warning_message("unable to allocate huge (1GiB) page, trying large (2MiB) pages instead (error %i)\n", errno); + lflags = ((lflags & ~MAP_HUGE_1GB) | MAP_HUGE_2MB); + p = mi_unix_mmapx(addr, size, try_alignment, protect_flags, lflags, lfd); + } + #endif + if (large_only) return p; + if (p == NULL) { + mi_atomic_write(&large_page_try_ok, 10); // on error, don't try again for the next N allocations + } + } + } + } + if (p == NULL) { + *is_large = false; + p = mi_unix_mmapx(addr, size, try_alignment, protect_flags, flags, fd); + #if defined(MADV_HUGEPAGE) + // Many Linux systems don't allow MAP_HUGETLB but they support instead + // transparent huge pages (THP). It is not required to call `madvise` with MADV_HUGE + // though since properly aligned allocations will already use large pages if available + // in that case -- in particular for our large regions (in `memory.c`). + // However, some systems only allow THP if called with explicit `madvise`, so + // when large OS pages are enabled for mimalloc, we call `madvice` anyways. + if (allow_large && use_large_os_page(size, try_alignment)) { + if (madvise(p, size, MADV_HUGEPAGE) == 0) { + *is_large = true; // possibly + }; + } + #endif + #if defined(__sun) + if (allow_large && use_large_os_page(size, try_alignment)) { + struct memcntl_mha cmd = {0}; + cmd.mha_pagesize = large_os_page_size; + cmd.mha_cmd = MHA_MAPSIZE_VA; + if (memcntl(p, size, MC_HAT_ADVISE, (caddr_t)&cmd, 0, 0) == 0) { + *is_large = true; + } + } + #endif + } + if (p == NULL) { + _mi_warning_message("unable to allocate OS memory (%zu bytes, error code: %i, address: %p, large only: %d, allow large: %d)\n", size, errno, addr, large_only, allow_large); + } + return p; +} +#endif + +// On 64-bit systems, we can do efficient aligned allocation by using +// the 4TiB to 30TiB area to allocate them. +#if (MI_INTPTR_SIZE >= 8) && (defined(_WIN32) || (defined(MI_OS_USE_MMAP) && !defined(MAP_ALIGNED))) +static volatile mi_decl_cache_align _Atomic(uintptr_t) aligned_base; + +// Return a 4MiB aligned address that is probably available +static void* mi_os_get_aligned_hint(size_t try_alignment, size_t size) { + if (try_alignment == 0 || try_alignment > MI_SEGMENT_SIZE) return NULL; + if ((size%MI_SEGMENT_SIZE) != 0) return NULL; + uintptr_t hint = mi_atomic_add(&aligned_base, size); + if (hint == 0 || hint > ((intptr_t)30<<40)) { // try to wrap around after 30TiB (area after 32TiB is used for huge OS pages) + uintptr_t init = ((uintptr_t)4 << 40); // start at 4TiB area + #if (MI_SECURE>0 || MI_DEBUG==0) // security: randomize start of aligned allocations unless in debug mode + uintptr_t r = _mi_heap_random_next(mi_get_default_heap()); + init = init + (MI_SEGMENT_SIZE * ((r>>17) & 0xFFFFF)); // (randomly 20 bits)*4MiB == 0 to 4TiB + #endif + mi_atomic_cas_strong(&aligned_base, init, hint + size); + hint = mi_atomic_add(&aligned_base, size); // this may still give 0 or > 30TiB but that is ok, it is a hint after all + } + if (hint%try_alignment != 0) return NULL; + return (void*)hint; +} +#else +static void* mi_os_get_aligned_hint(size_t try_alignment, size_t size) { + UNUSED(try_alignment); UNUSED(size); + return NULL; +} +#endif + + +// Primitive allocation from the OS. +// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned. +static void* mi_os_mem_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, mi_stats_t* stats) { + mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0); + if (size == 0) return NULL; + if (!commit) allow_large = false; + + void* p = NULL; + /* + if (commit && allow_large) { + p = _mi_os_try_alloc_from_huge_reserved(size, try_alignment); + if (p != NULL) { + *is_large = true; + return p; + } + } + */ + + #if defined(_WIN32) + int flags = MEM_RESERVE; + if (commit) flags |= MEM_COMMIT; + p = mi_win_virtual_alloc(NULL, size, try_alignment, flags, false, allow_large, is_large); + #elif defined(__wasi__) + *is_large = false; + p = mi_wasm_heap_grow(size, try_alignment); + #else + int protect_flags = (commit ? (PROT_WRITE | PROT_READ) : PROT_NONE); + p = mi_unix_mmap(NULL, size, try_alignment, protect_flags, false, allow_large, is_large); + #endif + mi_stat_counter_increase(stats->mmap_calls, 1); + if (p != NULL) { + _mi_stat_increase(&stats->reserved, size); + if (commit) { _mi_stat_increase(&stats->committed, size); } + } + return p; +} + + +// Primitive aligned allocation from the OS. +// This function guarantees the allocated memory is aligned. +static void* mi_os_mem_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, bool* is_large, mi_stats_t* stats) { + mi_assert_internal(alignment >= _mi_os_page_size() && ((alignment & (alignment - 1)) == 0)); + mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0); + if (!commit) allow_large = false; + if (!(alignment >= _mi_os_page_size() && ((alignment & (alignment - 1)) == 0))) return NULL; + size = _mi_align_up(size, _mi_os_page_size()); + + // try first with a hint (this will be aligned directly on Win 10+ or BSD) + void* p = mi_os_mem_alloc(size, alignment, commit, allow_large, is_large, stats); + if (p == NULL) return NULL; + + // if not aligned, free it, overallocate, and unmap around it + if (((uintptr_t)p % alignment != 0)) { + mi_os_mem_free(p, size, commit, stats); + if (size >= (SIZE_MAX - alignment)) return NULL; // overflow + size_t over_size = size + alignment; + +#if _WIN32 + // over-allocate and than re-allocate exactly at an aligned address in there. + // this may fail due to threads allocating at the same time so we + // retry this at most 3 times before giving up. + // (we can not decommit around the overallocation on Windows, because we can only + // free the original pointer, not one pointing inside the area) + int flags = MEM_RESERVE; + if (commit) flags |= MEM_COMMIT; + for (int tries = 0; tries < 3; tries++) { + // over-allocate to determine a virtual memory range + p = mi_os_mem_alloc(over_size, alignment, commit, false, is_large, stats); + if (p == NULL) return NULL; // error + if (((uintptr_t)p % alignment) == 0) { + // if p happens to be aligned, just decommit the left-over area + _mi_os_decommit((uint8_t*)p + size, over_size - size, stats); + break; + } + else { + // otherwise free and allocate at an aligned address in there + mi_os_mem_free(p, over_size, commit, stats); + void* aligned_p = mi_align_up_ptr(p, alignment); + p = mi_win_virtual_alloc(aligned_p, size, alignment, flags, false, allow_large, is_large); + if (p == aligned_p) break; // success! + if (p != NULL) { // should not happen? + mi_os_mem_free(p, size, commit, stats); + p = NULL; + } + } + } +#else + // overallocate... + p = mi_os_mem_alloc(over_size, alignment, commit, false, is_large, stats); + if (p == NULL) return NULL; + // and selectively unmap parts around the over-allocated area. + void* aligned_p = mi_align_up_ptr(p, alignment); + size_t pre_size = (uint8_t*)aligned_p - (uint8_t*)p; + size_t mid_size = _mi_align_up(size, _mi_os_page_size()); + size_t post_size = over_size - pre_size - mid_size; + mi_assert_internal(pre_size < over_size && post_size < over_size && mid_size >= size); + if (pre_size > 0) mi_os_mem_free(p, pre_size, commit, stats); + if (post_size > 0) mi_os_mem_free((uint8_t*)aligned_p + mid_size, post_size, commit, stats); + // we can return the aligned pointer on `mmap` systems + p = aligned_p; +#endif + } + + mi_assert_internal(p == NULL || (p != NULL && ((uintptr_t)p % alignment) == 0)); + return p; +} + +/* ----------------------------------------------------------- + OS API: alloc, free, alloc_aligned +----------------------------------------------------------- */ + +void* _mi_os_alloc(size_t size, mi_stats_t* stats) { + if (size == 0) return NULL; + size = _mi_os_good_alloc_size(size); + bool is_large = false; + return mi_os_mem_alloc(size, 0, true, false, &is_large, stats); +} + +void _mi_os_free_ex(void* p, size_t size, bool was_committed, mi_stats_t* stats) { + if (size == 0 || p == NULL) return; + size = _mi_os_good_alloc_size(size); + mi_os_mem_free(p, size, was_committed, stats); +} + +void _mi_os_free(void* p, size_t size, mi_stats_t* stats) { + _mi_os_free_ex(p, size, true, stats); +} + +void* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool* large, mi_os_tld_t* tld) +{ + if (size == 0) return NULL; + size = _mi_os_good_alloc_size(size); + alignment = _mi_align_up(alignment, _mi_os_page_size()); + bool allow_large = false; + if (large != NULL) { + allow_large = *large; + *large = false; + } + return mi_os_mem_alloc_aligned(size, alignment, commit, allow_large, (large!=NULL?large:&allow_large), tld->stats); +} + + + +/* ----------------------------------------------------------- + OS memory API: reset, commit, decommit, protect, unprotect. +----------------------------------------------------------- */ + + +// OS page align within a given area, either conservative (pages inside the area only), +// or not (straddling pages outside the area is possible) +static void* mi_os_page_align_areax(bool conservative, void* addr, size_t size, size_t* newsize) { + mi_assert(addr != NULL && size > 0); + if (newsize != NULL) *newsize = 0; + if (size == 0 || addr == NULL) return NULL; + + // page align conservatively within the range + void* start = (conservative ? mi_align_up_ptr(addr, _mi_os_page_size()) + : mi_align_down_ptr(addr, _mi_os_page_size())); + void* end = (conservative ? mi_align_down_ptr((uint8_t*)addr + size, _mi_os_page_size()) + : mi_align_up_ptr((uint8_t*)addr + size, _mi_os_page_size())); + ptrdiff_t diff = (uint8_t*)end - (uint8_t*)start; + if (diff <= 0) return NULL; + + mi_assert_internal((conservative && (size_t)diff <= size) || (!conservative && (size_t)diff >= size)); + if (newsize != NULL) *newsize = (size_t)diff; + return start; +} + +static void* mi_os_page_align_area_conservative(void* addr, size_t size, size_t* newsize) { + return mi_os_page_align_areax(true, addr, size, newsize); +} + +static void mi_mprotect_hint(int err) { +#if defined(MI_OS_USE_MMAP) && (MI_SECURE>=2) // guard page around every mimalloc page + if (err == ENOMEM) { + _mi_warning_message("the previous warning may have been caused by a low memory map limit.\n" + " On Linux this is controlled by the vm.max_map_count. For example:\n" + " > sudo sysctl -w vm.max_map_count=262144\n"); + } +#else + UNUSED(err); +#endif +} + +// Commit/Decommit memory. +// Usually commit is aligned liberal, while decommit is aligned conservative. +// (but not for the reset version where we want commit to be conservative as well) +static bool mi_os_commitx(void* addr, size_t size, bool commit, bool conservative, bool* is_zero, mi_stats_t* stats) { + // page align in the range, commit liberally, decommit conservative + if (is_zero != NULL) { *is_zero = false; } + size_t csize; + void* start = mi_os_page_align_areax(conservative, addr, size, &csize); + if (csize == 0) return true; // || _mi_os_is_huge_reserved(addr)) + int err = 0; + if (commit) { + _mi_stat_increase(&stats->committed, csize); + _mi_stat_counter_increase(&stats->commit_calls, 1); + } + else { + _mi_stat_decrease(&stats->committed, csize); + } + + #if defined(_WIN32) + if (commit) { + // if the memory was already committed, the call succeeds but it is not zero'd + // *is_zero = true; + void* p = VirtualAlloc(start, csize, MEM_COMMIT, PAGE_READWRITE); + err = (p == start ? 0 : GetLastError()); + } + else { + BOOL ok = VirtualFree(start, csize, MEM_DECOMMIT); + err = (ok ? 0 : GetLastError()); + } + #elif defined(__wasi__) + // WebAssembly guests can't control memory protection + #elif defined(MAP_FIXED) + if (!commit) { + // use mmap with MAP_FIXED to discard the existing memory (and reduce commit charge) + void* p = mmap(start, csize, PROT_NONE, (MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE), -1, 0); + if (p != start) { err = errno; } + } + else { + // for commit, just change the protection + err = mprotect(start, csize, (PROT_READ | PROT_WRITE)); + if (err != 0) { err = errno; } + } + #else + err = mprotect(start, csize, (commit ? (PROT_READ | PROT_WRITE) : PROT_NONE)); + if (err != 0) { err = errno; } + #endif + if (err != 0) { + _mi_warning_message("%s error: start: %p, csize: 0x%x, err: %i\n", commit ? "commit" : "decommit", start, csize, err); + mi_mprotect_hint(err); + } + mi_assert_internal(err == 0); + return (err == 0); +} + +bool _mi_os_commit(void* addr, size_t size, bool* is_zero, mi_stats_t* stats) { + return mi_os_commitx(addr, size, true, false /* liberal */, is_zero, stats); +} + +bool _mi_os_decommit(void* addr, size_t size, mi_stats_t* stats) { + bool is_zero; + return mi_os_commitx(addr, size, false, true /* conservative */, &is_zero, stats); +} + +bool _mi_os_commit_unreset(void* addr, size_t size, bool* is_zero, mi_stats_t* stats) { + return mi_os_commitx(addr, size, true, true /* conservative */, is_zero, stats); +} + +// Signal to the OS that the address range is no longer in use +// but may be used later again. This will release physical memory +// pages and reduce swapping while keeping the memory committed. +// We page align to a conservative area inside the range to reset. +static bool mi_os_resetx(void* addr, size_t size, bool reset, mi_stats_t* stats) { + // page align conservatively within the range + size_t csize; + void* start = mi_os_page_align_area_conservative(addr, size, &csize); + if (csize == 0) return true; // || _mi_os_is_huge_reserved(addr) + if (reset) _mi_stat_increase(&stats->reset, csize); + else _mi_stat_decrease(&stats->reset, csize); + if (!reset) return true; // nothing to do on unreset! + + #if (MI_DEBUG>1) + if (MI_SECURE==0) { + memset(start, 0, csize); // pretend it is eagerly reset + } + #endif + +#if defined(_WIN32) + // Testing shows that for us (on `malloc-large`) MEM_RESET is 2x faster than DiscardVirtualMemory + void* p = VirtualAlloc(start, csize, MEM_RESET, PAGE_READWRITE); + mi_assert_internal(p == start); + #if 1 + if (p == start && start != NULL) { + VirtualUnlock(start,csize); // VirtualUnlock after MEM_RESET removes the memory from the working set + } + #endif + if (p != start) return false; +#else +#if defined(MADV_FREE) + static int advice = MADV_FREE; + int err = madvise(start, csize, advice); + if (err != 0 && errno == EINVAL && advice == MADV_FREE) { + // if MADV_FREE is not supported, fall back to MADV_DONTNEED from now on + advice = MADV_DONTNEED; + err = madvise(start, csize, advice); + } +#elif defined(__wasi__) + int err = 0; +#else + int err = madvise(start, csize, MADV_DONTNEED); +#endif + if (err != 0) { + _mi_warning_message("madvise reset error: start: %p, csize: 0x%x, errno: %i\n", start, csize, errno); + } + //mi_assert(err == 0); + if (err != 0) return false; +#endif + return true; +} + +// Signal to the OS that the address range is no longer in use +// but may be used later again. This will release physical memory +// pages and reduce swapping while keeping the memory committed. +// We page align to a conservative area inside the range to reset. +bool _mi_os_reset(void* addr, size_t size, mi_stats_t* stats) { + if (mi_option_is_enabled(mi_option_reset_decommits)) { + return _mi_os_decommit(addr, size, stats); + } + else { + return mi_os_resetx(addr, size, true, stats); + } +} + +bool _mi_os_unreset(void* addr, size_t size, bool* is_zero, mi_stats_t* stats) { + if (mi_option_is_enabled(mi_option_reset_decommits)) { + return _mi_os_commit_unreset(addr, size, is_zero, stats); // re-commit it (conservatively!) + } + else { + *is_zero = false; + return mi_os_resetx(addr, size, false, stats); + } +} + + +// Protect a region in memory to be not accessible. +static bool mi_os_protectx(void* addr, size_t size, bool protect) { + // page align conservatively within the range + size_t csize = 0; + void* start = mi_os_page_align_area_conservative(addr, size, &csize); + if (csize == 0) return false; + /* + if (_mi_os_is_huge_reserved(addr)) { + _mi_warning_message("cannot mprotect memory allocated in huge OS pages\n"); + } + */ + int err = 0; +#ifdef _WIN32 + DWORD oldprotect = 0; + BOOL ok = VirtualProtect(start, csize, protect ? PAGE_NOACCESS : PAGE_READWRITE, &oldprotect); + err = (ok ? 0 : GetLastError()); +#elif defined(__wasi__) + err = 0; +#else + err = mprotect(start, csize, protect ? PROT_NONE : (PROT_READ | PROT_WRITE)); + if (err != 0) { err = errno; } +#endif + if (err != 0) { + _mi_warning_message("mprotect error: start: %p, csize: 0x%x, err: %i\n", start, csize, err); + mi_mprotect_hint(err); + } + return (err == 0); +} + +bool _mi_os_protect(void* addr, size_t size) { + return mi_os_protectx(addr, size, true); +} + +bool _mi_os_unprotect(void* addr, size_t size) { + return mi_os_protectx(addr, size, false); +} + + + +bool _mi_os_shrink(void* p, size_t oldsize, size_t newsize, mi_stats_t* stats) { + // page align conservatively within the range + mi_assert_internal(oldsize > newsize && p != NULL); + if (oldsize < newsize || p == NULL) return false; + if (oldsize == newsize) return true; + + // oldsize and newsize should be page aligned or we cannot shrink precisely + void* addr = (uint8_t*)p + newsize; + size_t size = 0; + void* start = mi_os_page_align_area_conservative(addr, oldsize - newsize, &size); + if (size == 0 || start != addr) return false; + +#ifdef _WIN32 + // we cannot shrink on windows, but we can decommit + return _mi_os_decommit(start, size, stats); +#else + return mi_os_mem_free(start, size, true, stats); +#endif +} + + +/* ---------------------------------------------------------------------------- +Support for allocating huge OS pages (1Gib) that are reserved up-front +and possibly associated with a specific NUMA node. (use `numa_node>=0`) +-----------------------------------------------------------------------------*/ +#define MI_HUGE_OS_PAGE_SIZE (GiB) + +#if defined(_WIN32) && (MI_INTPTR_SIZE >= 8) +static void* mi_os_alloc_huge_os_pagesx(void* addr, size_t size, int numa_node) +{ + mi_assert_internal(size%GiB == 0); + mi_assert_internal(addr != NULL); + const DWORD flags = MEM_LARGE_PAGES | MEM_COMMIT | MEM_RESERVE; + + mi_win_enable_large_os_pages(); + + #if defined(MEM_EXTENDED_PARAMETER_TYPE_BITS) + MEM_EXTENDED_PARAMETER params[3] = { {{0,0},{0}},{{0,0},{0}},{{0,0},{0}} }; + // on modern Windows try use NtAllocateVirtualMemoryEx for 1GiB huge pages + static bool mi_huge_pages_available = true; + if (pNtAllocateVirtualMemoryEx != NULL && mi_huge_pages_available) { + #ifndef MEM_EXTENDED_PARAMETER_NONPAGED_HUGE + #define MEM_EXTENDED_PARAMETER_NONPAGED_HUGE (0x10) + #endif + params[0].Type = 5; // == MemExtendedParameterAttributeFlags; + params[0].ULong64 = MEM_EXTENDED_PARAMETER_NONPAGED_HUGE; + ULONG param_count = 1; + if (numa_node >= 0) { + param_count++; + params[1].Type = MemExtendedParameterNumaNode; + params[1].ULong = (unsigned)numa_node; + } + SIZE_T psize = size; + void* base = addr; + NTSTATUS err = (*pNtAllocateVirtualMemoryEx)(GetCurrentProcess(), &base, &psize, flags, PAGE_READWRITE, params, param_count); + if (err == 0 && base != NULL) { + return base; + } + else { + // fall back to regular large pages + mi_huge_pages_available = false; // don't try further huge pages + _mi_warning_message("unable to allocate using huge (1gb) pages, trying large (2mb) pages instead (status 0x%lx)\n", err); + } + } + // on modern Windows try use VirtualAlloc2 for numa aware large OS page allocation + if (pVirtualAlloc2 != NULL && numa_node >= 0) { + params[0].Type = MemExtendedParameterNumaNode; + params[0].ULong = (unsigned)numa_node; + return (*pVirtualAlloc2)(GetCurrentProcess(), addr, size, flags, PAGE_READWRITE, params, 1); + } + #else + UNUSED(numa_node); + #endif + // otherwise use regular virtual alloc on older windows + return VirtualAlloc(addr, size, flags, PAGE_READWRITE); +} + +#elif defined(MI_OS_USE_MMAP) && (MI_INTPTR_SIZE >= 8) && !defined(__HAIKU__) +#include +#ifndef MPOL_PREFERRED +#define MPOL_PREFERRED 1 +#endif +#if defined(SYS_mbind) +static long mi_os_mbind(void* start, unsigned long len, unsigned long mode, const unsigned long* nmask, unsigned long maxnode, unsigned flags) { + return syscall(SYS_mbind, start, len, mode, nmask, maxnode, flags); +} +#else +static long mi_os_mbind(void* start, unsigned long len, unsigned long mode, const unsigned long* nmask, unsigned long maxnode, unsigned flags) { + UNUSED(start); UNUSED(len); UNUSED(mode); UNUSED(nmask); UNUSED(maxnode); UNUSED(flags); + return 0; +} +#endif +static void* mi_os_alloc_huge_os_pagesx(void* addr, size_t size, int numa_node) { + mi_assert_internal(size%GiB == 0); + bool is_large = true; + void* p = mi_unix_mmap(addr, size, MI_SEGMENT_SIZE, PROT_READ | PROT_WRITE, true, true, &is_large); + if (p == NULL) return NULL; + if (numa_node >= 0 && numa_node < 8*MI_INTPTR_SIZE) { // at most 64 nodes + uintptr_t numa_mask = (1UL << numa_node); + // TODO: does `mbind` work correctly for huge OS pages? should we + // use `set_mempolicy` before calling mmap instead? + // see: + long err = mi_os_mbind(p, size, MPOL_PREFERRED, &numa_mask, 8*MI_INTPTR_SIZE, 0); + if (err != 0) { + _mi_warning_message("failed to bind huge (1gb) pages to numa node %d: %s\n", numa_node, strerror(errno)); + } + } + return p; +} +#else +static void* mi_os_alloc_huge_os_pagesx(void* addr, size_t size, int numa_node) { + UNUSED(addr); UNUSED(size); UNUSED(numa_node); + return NULL; +} +#endif + +#if (MI_INTPTR_SIZE >= 8) +// To ensure proper alignment, use our own area for huge OS pages +static mi_decl_cache_align _Atomic(uintptr_t) mi_huge_start; // = 0 + +// Claim an aligned address range for huge pages +static uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) { + if (total_size != NULL) *total_size = 0; + const size_t size = pages * MI_HUGE_OS_PAGE_SIZE; + + uintptr_t start = 0; + uintptr_t end = 0; + uintptr_t expected; + do { + start = expected = mi_atomic_read_relaxed(&mi_huge_start); + if (start == 0) { + // Initialize the start address after the 32TiB area + start = ((uintptr_t)32 << 40); // 32TiB virtual start address +#if (MI_SECURE>0 || MI_DEBUG==0) // security: randomize start of huge pages unless in debug mode + uintptr_t r = _mi_heap_random_next(mi_get_default_heap()); + start = start + ((uintptr_t)MI_HUGE_OS_PAGE_SIZE * ((r>>17) & 0x0FFF)); // (randomly 12bits)*1GiB == between 0 to 4TiB +#endif + } + end = start + size; + mi_assert_internal(end % MI_SEGMENT_SIZE == 0); + } while (!mi_atomic_cas_strong(&mi_huge_start, end, expected)); + + if (total_size != NULL) *total_size = size; + return (uint8_t*)start; +} +#else +static uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) { + UNUSED(pages); + if (total_size != NULL) *total_size = 0; + return NULL; +} +#endif + +// Allocate MI_SEGMENT_SIZE aligned huge pages +void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_msecs, size_t* pages_reserved, size_t* psize) { + if (psize != NULL) *psize = 0; + if (pages_reserved != NULL) *pages_reserved = 0; + size_t size = 0; + uint8_t* start = mi_os_claim_huge_pages(pages, &size); + if (start == NULL) return NULL; // or 32-bit systems + + // Allocate one page at the time but try to place them contiguously + // We allocate one page at the time to be able to abort if it takes too long + // or to at least allocate as many as available on the system. + mi_msecs_t start_t = _mi_clock_start(); + size_t page; + for (page = 0; page < pages; page++) { + // allocate a page + void* addr = start + (page * MI_HUGE_OS_PAGE_SIZE); + void* p = mi_os_alloc_huge_os_pagesx(addr, MI_HUGE_OS_PAGE_SIZE, numa_node); + + // Did we succeed at a contiguous address? + if (p != addr) { + // no success, issue a warning and break + if (p != NULL) { + _mi_warning_message("could not allocate contiguous huge page %zu at %p\n", page, addr); + _mi_os_free(p, MI_HUGE_OS_PAGE_SIZE, &_mi_stats_main); + } + break; + } + + // success, record it + _mi_stat_increase(&_mi_stats_main.committed, MI_HUGE_OS_PAGE_SIZE); + _mi_stat_increase(&_mi_stats_main.reserved, MI_HUGE_OS_PAGE_SIZE); + + // check for timeout + if (max_msecs > 0) { + mi_msecs_t elapsed = _mi_clock_end(start_t); + if (page >= 1) { + mi_msecs_t estimate = ((elapsed / (page+1)) * pages); + if (estimate > 2*max_msecs) { // seems like we are going to timeout, break + elapsed = max_msecs + 1; + } + } + if (elapsed > max_msecs) { + _mi_warning_message("huge page allocation timed out\n"); + break; + } + } + } + mi_assert_internal(page*MI_HUGE_OS_PAGE_SIZE <= size); + if (pages_reserved != NULL) *pages_reserved = page; + if (psize != NULL) *psize = page * MI_HUGE_OS_PAGE_SIZE; + return (page == 0 ? NULL : start); +} + +// free every huge page in a range individually (as we allocated per page) +// note: needed with VirtualAlloc but could potentially be done in one go on mmap'd systems. +void _mi_os_free_huge_pages(void* p, size_t size, mi_stats_t* stats) { + if (p==NULL || size==0) return; + uint8_t* base = (uint8_t*)p; + while (size >= MI_HUGE_OS_PAGE_SIZE) { + _mi_os_free(base, MI_HUGE_OS_PAGE_SIZE, stats); + size -= MI_HUGE_OS_PAGE_SIZE; + } +} + +/* ---------------------------------------------------------------------------- +Support NUMA aware allocation +-----------------------------------------------------------------------------*/ +#ifdef _WIN32 +static size_t mi_os_numa_nodex() { + USHORT numa_node = 0; + if (pGetCurrentProcessorNumberEx != NULL && pGetNumaProcessorNodeEx != NULL) { + // Extended API is supported + PROCESSOR_NUMBER pnum; + (*pGetCurrentProcessorNumberEx)(&pnum); + USHORT nnode = 0; + BOOL ok = (*pGetNumaProcessorNodeEx)(&pnum, &nnode); + if (ok) numa_node = nnode; + } + else { + // Vista or earlier, use older API that is limited to 64 processors. Issue #277 + DWORD pnum = GetCurrentProcessorNumber(); + UCHAR nnode = 0; + BOOL ok = GetNumaProcessorNode((UCHAR)pnum, &nnode); + if (ok) numa_node = nnode; + } + return numa_node; +} + +static size_t mi_os_numa_node_countx(void) { + ULONG numa_max = 0; + GetNumaHighestNodeNumber(&numa_max); + // find the highest node number that has actual processors assigned to it. Issue #282 + while(numa_max > 0) { + if (pGetNumaNodeProcessorMaskEx != NULL) { + // Extended API is supported + GROUP_AFFINITY affinity; + if ((*pGetNumaNodeProcessorMaskEx)((USHORT)numa_max, &affinity)) { + if (affinity.Mask != 0) break; // found the maximum non-empty node + } + } + else { + // Vista or earlier, use older API that is limited to 64 processors. + ULONGLONG mask; + if (GetNumaNodeProcessorMask((UCHAR)numa_max, &mask)) { + if (mask != 0) break; // found the maximum non-empty node + }; + } + // max node was invalid or had no processor assigned, try again + numa_max--; + } + return ((size_t)numa_max + 1); +} +#elif defined(__linux__) +#include // getcpu +#include // access + +static size_t mi_os_numa_nodex(void) { +#ifdef SYS_getcpu + unsigned long node = 0; + unsigned long ncpu = 0; + long err = syscall(SYS_getcpu, &ncpu, &node, NULL); + if (err != 0) return 0; + return node; +#else + return 0; +#endif +} +static size_t mi_os_numa_node_countx(void) { + char buf[128]; + unsigned node = 0; + for(node = 0; node < 256; node++) { + // enumerate node entries -- todo: it there a more efficient way to do this? (but ensure there is no allocation) + snprintf(buf, 127, "/sys/devices/system/node/node%u", node + 1); + if (access(buf,R_OK) != 0) break; + } + return (node+1); +} +#else +static size_t mi_os_numa_nodex(void) { + return 0; +} +static size_t mi_os_numa_node_countx(void) { + return 1; +} +#endif + +size_t _mi_numa_node_count = 0; // cache the node count + +size_t _mi_os_numa_node_count_get(void) { + if (mi_unlikely(_mi_numa_node_count <= 0)) { + long ncount = mi_option_get(mi_option_use_numa_nodes); // given explicitly? + if (ncount <= 0) ncount = (long)mi_os_numa_node_countx(); // or detect dynamically + _mi_numa_node_count = (size_t)(ncount <= 0 ? 1 : ncount); + _mi_verbose_message("using %zd numa regions\n", _mi_numa_node_count); + } + mi_assert_internal(_mi_numa_node_count >= 1); + return _mi_numa_node_count; +} + +int _mi_os_numa_node_get(mi_os_tld_t* tld) { + UNUSED(tld); + size_t numa_count = _mi_os_numa_node_count(); + if (numa_count<=1) return 0; // optimize on single numa node systems: always node 0 + // never more than the node count and >= 0 + size_t numa_node = mi_os_numa_nodex(); + if (numa_node >= numa_count) { numa_node = numa_node % numa_count; } + return (int)numa_node; +} diff --git a/extlib/mimalloc/src/page-queue.c b/extlib/mimalloc/src/page-queue.c new file mode 100644 index 0000000..ea21301 --- /dev/null +++ b/extlib/mimalloc/src/page-queue.c @@ -0,0 +1,368 @@ +/*---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* ----------------------------------------------------------- + Definition of page queues for each block size +----------------------------------------------------------- */ + +#ifndef MI_IN_PAGE_C +#error "this file should be included from 'page.c'" +#endif + +/* ----------------------------------------------------------- + Minimal alignment in machine words (i.e. `sizeof(void*)`) +----------------------------------------------------------- */ + +#if (MI_MAX_ALIGN_SIZE > 4*MI_INTPTR_SIZE) + #error "define alignment for more than 4x word size for this platform" +#elif (MI_MAX_ALIGN_SIZE > 2*MI_INTPTR_SIZE) + #define MI_ALIGN4W // 4 machine words minimal alignment +#elif (MI_MAX_ALIGN_SIZE > MI_INTPTR_SIZE) + #define MI_ALIGN2W // 2 machine words minimal alignment +#else + // ok, default alignment is 1 word +#endif + + +/* ----------------------------------------------------------- + Queue query +----------------------------------------------------------- */ + + +static inline bool mi_page_queue_is_huge(const mi_page_queue_t* pq) { + return (pq->block_size == (MI_LARGE_OBJ_SIZE_MAX+sizeof(uintptr_t))); +} + +static inline bool mi_page_queue_is_full(const mi_page_queue_t* pq) { + return (pq->block_size == (MI_LARGE_OBJ_SIZE_MAX+(2*sizeof(uintptr_t)))); +} + +static inline bool mi_page_queue_is_special(const mi_page_queue_t* pq) { + return (pq->block_size > MI_LARGE_OBJ_SIZE_MAX); +} + +/* ----------------------------------------------------------- + Bins +----------------------------------------------------------- */ + +// Bit scan reverse: return the index of the highest bit. +static inline uint8_t mi_bsr32(uint32_t x); + +#if defined(_MSC_VER) +#include +static inline uint8_t mi_bsr32(uint32_t x) { + uint32_t idx; + _BitScanReverse((DWORD*)&idx, x); + return (uint8_t)idx; +} +#elif defined(__GNUC__) || defined(__clang__) +static inline uint8_t mi_bsr32(uint32_t x) { + return (31 - __builtin_clz(x)); +} +#else +static inline uint8_t mi_bsr32(uint32_t x) { + // de Bruijn multiplication, see + static const uint8_t debruijn[32] = { + 31, 0, 22, 1, 28, 23, 18, 2, 29, 26, 24, 10, 19, 7, 3, 12, + 30, 21, 27, 17, 25, 9, 6, 11, 20, 16, 8, 5, 15, 4, 14, 13, + }; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + return debruijn[(x*0x076be629) >> 27]; +} +#endif + +// Bit scan reverse: return the index of the highest bit. +uint8_t _mi_bsr(uintptr_t x) { + if (x == 0) return 0; +#if MI_INTPTR_SIZE==8 + uint32_t hi = (x >> 32); + return (hi == 0 ? mi_bsr32((uint32_t)x) : 32 + mi_bsr32(hi)); +#elif MI_INTPTR_SIZE==4 + return mi_bsr32(x); +#else +# error "define bsr for non-32 or 64-bit platforms" +#endif +} + +// Return the bin for a given field size. +// Returns MI_BIN_HUGE if the size is too large. +// We use `wsize` for the size in "machine word sizes", +// i.e. byte size == `wsize*sizeof(void*)`. +extern inline uint8_t _mi_bin(size_t size) { + size_t wsize = _mi_wsize_from_size(size); + uint8_t bin; + if (wsize <= 1) { + bin = 1; + } + #if defined(MI_ALIGN4W) + else if (wsize <= 4) { + bin = (uint8_t)((wsize+1)&~1); // round to double word sizes + } + #elif defined(MI_ALIGN2W) + else if (wsize <= 8) { + bin = (uint8_t)((wsize+1)&~1); // round to double word sizes + } + #else + else if (wsize <= 8) { + bin = (uint8_t)wsize; + } + #endif + else if (wsize > MI_LARGE_OBJ_WSIZE_MAX) { + bin = MI_BIN_HUGE; + } + else { + #if defined(MI_ALIGN4W) + if (wsize <= 16) { wsize = (wsize+3)&~3; } // round to 4x word sizes + #endif + wsize--; + // find the highest bit + uint8_t b = mi_bsr32((uint32_t)wsize); + // and use the top 3 bits to determine the bin (~12.5% worst internal fragmentation). + // - adjust with 3 because we use do not round the first 8 sizes + // which each get an exact bin + bin = ((b << 2) + (uint8_t)((wsize >> (b - 2)) & 0x03)) - 3; + mi_assert_internal(bin < MI_BIN_HUGE); + } + mi_assert_internal(bin > 0 && bin <= MI_BIN_HUGE); + return bin; +} + + + +/* ----------------------------------------------------------- + Queue of pages with free blocks +----------------------------------------------------------- */ + +size_t _mi_bin_size(uint8_t bin) { + return _mi_heap_empty.pages[bin].block_size; +} + +// Good size for allocation +size_t mi_good_size(size_t size) mi_attr_noexcept { + if (size <= MI_LARGE_OBJ_SIZE_MAX) { + return _mi_bin_size(_mi_bin(size)); + } + else { + return _mi_align_up(size,_mi_os_page_size()); + } +} + +#if (MI_DEBUG>1) +static bool mi_page_queue_contains(mi_page_queue_t* queue, const mi_page_t* page) { + mi_assert_internal(page != NULL); + mi_page_t* list = queue->first; + while (list != NULL) { + mi_assert_internal(list->next == NULL || list->next->prev == list); + mi_assert_internal(list->prev == NULL || list->prev->next == list); + if (list == page) break; + list = list->next; + } + return (list == page); +} + +#endif + +#if (MI_DEBUG>1) +static bool mi_heap_contains_queue(const mi_heap_t* heap, const mi_page_queue_t* pq) { + return (pq >= &heap->pages[0] && pq <= &heap->pages[MI_BIN_FULL]); +} +#endif + +static mi_page_queue_t* mi_page_queue_of(const mi_page_t* page) { + uint8_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : _mi_bin(page->xblock_size)); + mi_heap_t* heap = mi_page_heap(page); + mi_assert_internal(heap != NULL && bin <= MI_BIN_FULL); + mi_page_queue_t* pq = &heap->pages[bin]; + mi_assert_internal(bin >= MI_BIN_HUGE || page->xblock_size == pq->block_size); + mi_assert_expensive(mi_page_queue_contains(pq, page)); + return pq; +} + +static mi_page_queue_t* mi_heap_page_queue_of(mi_heap_t* heap, const mi_page_t* page) { + uint8_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : _mi_bin(page->xblock_size)); + mi_assert_internal(bin <= MI_BIN_FULL); + mi_page_queue_t* pq = &heap->pages[bin]; + mi_assert_internal(mi_page_is_in_full(page) || page->xblock_size == pq->block_size); + return pq; +} + +// The current small page array is for efficiency and for each +// small size (up to 256) it points directly to the page for that +// size without having to compute the bin. This means when the +// current free page queue is updated for a small bin, we need to update a +// range of entries in `_mi_page_small_free`. +static inline void mi_heap_queue_first_update(mi_heap_t* heap, const mi_page_queue_t* pq) { + mi_assert_internal(mi_heap_contains_queue(heap,pq)); + size_t size = pq->block_size; + if (size > MI_SMALL_SIZE_MAX) return; + + mi_page_t* page = pq->first; + if (pq->first == NULL) page = (mi_page_t*)&_mi_page_empty; + + // find index in the right direct page array + size_t start; + size_t idx = _mi_wsize_from_size(size); + mi_page_t** pages_free = heap->pages_free_direct; + + if (pages_free[idx] == page) return; // already set + + // find start slot + if (idx<=1) { + start = 0; + } + else { + // find previous size; due to minimal alignment upto 3 previous bins may need to be skipped + uint8_t bin = _mi_bin(size); + const mi_page_queue_t* prev = pq - 1; + while( bin == _mi_bin(prev->block_size) && prev > &heap->pages[0]) { + prev--; + } + start = 1 + _mi_wsize_from_size(prev->block_size); + if (start > idx) start = idx; + } + + // set size range to the right page + mi_assert(start <= idx); + for (size_t sz = start; sz <= idx; sz++) { + pages_free[sz] = page; + } +} + +/* +static bool mi_page_queue_is_empty(mi_page_queue_t* queue) { + return (queue->first == NULL); +} +*/ + +static void mi_page_queue_remove(mi_page_queue_t* queue, mi_page_t* page) { + mi_assert_internal(page != NULL); + mi_assert_expensive(mi_page_queue_contains(queue, page)); + mi_assert_internal(page->xblock_size == queue->block_size || (page->xblock_size > MI_LARGE_OBJ_SIZE_MAX && mi_page_queue_is_huge(queue)) || (mi_page_is_in_full(page) && mi_page_queue_is_full(queue))); + mi_heap_t* heap = mi_page_heap(page); + if (page->prev != NULL) page->prev->next = page->next; + if (page->next != NULL) page->next->prev = page->prev; + if (page == queue->last) queue->last = page->prev; + if (page == queue->first) { + queue->first = page->next; + // update first + mi_assert_internal(mi_heap_contains_queue(heap, queue)); + mi_heap_queue_first_update(heap,queue); + } + heap->page_count--; + page->next = NULL; + page->prev = NULL; + // mi_atomic_write_ptr(mi_atomic_cast(void*, &page->heap), NULL); + mi_page_set_in_full(page,false); +} + + +static void mi_page_queue_push(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) { + mi_assert_internal(mi_page_heap(page) == heap); + mi_assert_internal(!mi_page_queue_contains(queue, page)); + mi_assert_internal(_mi_page_segment(page)->page_kind != MI_PAGE_HUGE); + mi_assert_internal(page->xblock_size == queue->block_size || + (page->xblock_size > MI_LARGE_OBJ_SIZE_MAX && mi_page_queue_is_huge(queue)) || + (mi_page_is_in_full(page) && mi_page_queue_is_full(queue))); + + mi_page_set_in_full(page, mi_page_queue_is_full(queue)); + // mi_atomic_write_ptr(mi_atomic_cast(void*, &page->heap), heap); + page->next = queue->first; + page->prev = NULL; + if (queue->first != NULL) { + mi_assert_internal(queue->first->prev == NULL); + queue->first->prev = page; + queue->first = page; + } + else { + queue->first = queue->last = page; + } + + // update direct + mi_heap_queue_first_update(heap, queue); + heap->page_count++; +} + + +static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) { + mi_assert_internal(page != NULL); + mi_assert_expensive(mi_page_queue_contains(from, page)); + mi_assert_expensive(!mi_page_queue_contains(to, page)); + mi_assert_internal((page->xblock_size == to->block_size && page->xblock_size == from->block_size) || + (page->xblock_size == to->block_size && mi_page_queue_is_full(from)) || + (page->xblock_size == from->block_size && mi_page_queue_is_full(to)) || + (page->xblock_size > MI_LARGE_OBJ_SIZE_MAX && mi_page_queue_is_huge(to)) || + (page->xblock_size > MI_LARGE_OBJ_SIZE_MAX && mi_page_queue_is_full(to))); + + mi_heap_t* heap = mi_page_heap(page); + if (page->prev != NULL) page->prev->next = page->next; + if (page->next != NULL) page->next->prev = page->prev; + if (page == from->last) from->last = page->prev; + if (page == from->first) { + from->first = page->next; + // update first + mi_assert_internal(mi_heap_contains_queue(heap, from)); + mi_heap_queue_first_update(heap, from); + } + + page->prev = to->last; + page->next = NULL; + if (to->last != NULL) { + mi_assert_internal(heap == mi_page_heap(to->last)); + to->last->next = page; + to->last = page; + } + else { + to->first = page; + to->last = page; + mi_heap_queue_first_update(heap, to); + } + + mi_page_set_in_full(page, mi_page_queue_is_full(to)); +} + +// Only called from `mi_heap_absorb`. +size_t _mi_page_queue_append(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_queue_t* append) { + mi_assert_internal(mi_heap_contains_queue(heap,pq)); + mi_assert_internal(pq->block_size == append->block_size); + + if (append->first==NULL) return 0; + + // set append pages to new heap and count + size_t count = 0; + for (mi_page_t* page = append->first; page != NULL; page = page->next) { + // inline `mi_page_set_heap` to avoid wrong assertion during absorption; + // in this case it is ok to be delayed freeing since both "to" and "from" heap are still alive. + mi_atomic_write(&page->xheap, (uintptr_t)heap); + // set the flag to delayed free (not overriding NEVER_DELAYED_FREE) which has as a + // side effect that it spins until any DELAYED_FREEING is finished. This ensures + // that after appending only the new heap will be used for delayed free operations. + _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, false); + count++; + } + + if (pq->last==NULL) { + // take over afresh + mi_assert_internal(pq->first==NULL); + pq->first = append->first; + pq->last = append->last; + mi_heap_queue_first_update(heap, pq); + } + else { + // append to end + mi_assert_internal(pq->last!=NULL); + mi_assert_internal(append->first!=NULL); + pq->last->next = append->first; + append->first->prev = pq->last; + pq->last = append->last; + } + return count; +} diff --git a/extlib/mimalloc/src/page.c b/extlib/mimalloc/src/page.c new file mode 100644 index 0000000..c8a4e54 --- /dev/null +++ b/extlib/mimalloc/src/page.c @@ -0,0 +1,847 @@ +/*---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* ----------------------------------------------------------- + The core of the allocator. Every segment contains + pages of a {certain block size. The main function + exported is `mi_malloc_generic`. +----------------------------------------------------------- */ + +#include "mimalloc.h" +#include "mimalloc-internal.h" +#include "mimalloc-atomic.h" + +/* ----------------------------------------------------------- + Definition of page queues for each block size +----------------------------------------------------------- */ + +#define MI_IN_PAGE_C +#include "page-queue.c" +#undef MI_IN_PAGE_C + + +/* ----------------------------------------------------------- + Page helpers +----------------------------------------------------------- */ + +// Index a block in a page +static inline mi_block_t* mi_page_block_at(const mi_page_t* page, void* page_start, size_t block_size, size_t i) { + UNUSED(page); + mi_assert_internal(page != NULL); + mi_assert_internal(i <= page->reserved); + return (mi_block_t*)((uint8_t*)page_start + (i * block_size)); +} + +static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t size, mi_tld_t* tld); +static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld); + +#if (MI_DEBUG>=3) +static size_t mi_page_list_count(mi_page_t* page, mi_block_t* head) { + size_t count = 0; + while (head != NULL) { + mi_assert_internal(page == _mi_ptr_page(head)); + count++; + head = mi_block_next(page, head); + } + return count; +} + +/* +// Start of the page available memory +static inline uint8_t* mi_page_area(const mi_page_t* page) { + return _mi_page_start(_mi_page_segment(page), page, NULL); +} +*/ + +static bool mi_page_list_is_valid(mi_page_t* page, mi_block_t* p) { + size_t psize; + uint8_t* page_area = _mi_page_start(_mi_page_segment(page), page, &psize); + mi_block_t* start = (mi_block_t*)page_area; + mi_block_t* end = (mi_block_t*)(page_area + psize); + while(p != NULL) { + if (p < start || p >= end) return false; + p = mi_block_next(page, p); + } + return true; +} + +static bool mi_page_is_valid_init(mi_page_t* page) { + mi_assert_internal(page->xblock_size > 0); + mi_assert_internal(page->used <= page->capacity); + mi_assert_internal(page->capacity <= page->reserved); + + const size_t bsize = mi_page_block_size(page); + mi_segment_t* segment = _mi_page_segment(page); + uint8_t* start = _mi_page_start(segment,page,NULL); + mi_assert_internal(start == _mi_segment_page_start(segment,page,bsize,NULL,NULL)); + //mi_assert_internal(start + page->capacity*page->block_size == page->top); + + mi_assert_internal(mi_page_list_is_valid(page,page->free)); + mi_assert_internal(mi_page_list_is_valid(page,page->local_free)); + + #if MI_DEBUG>3 // generally too expensive to check this + if (page->flags.is_zero) { + for(mi_block_t* block = page->free; block != NULL; mi_block_next(page,block)) { + mi_assert_expensive(mi_mem_is_zero(block + 1, page->block_size - sizeof(mi_block_t))); + } + } + #endif + + mi_block_t* tfree = mi_page_thread_free(page); + mi_assert_internal(mi_page_list_is_valid(page, tfree)); + //size_t tfree_count = mi_page_list_count(page, tfree); + //mi_assert_internal(tfree_count <= page->thread_freed + 1); + + size_t free_count = mi_page_list_count(page, page->free) + mi_page_list_count(page, page->local_free); + mi_assert_internal(page->used + free_count == page->capacity); + + return true; +} + +bool _mi_page_is_valid(mi_page_t* page) { + mi_assert_internal(mi_page_is_valid_init(page)); + #if MI_SECURE + mi_assert_internal(page->keys[0] != 0); + #endif + if (mi_page_heap(page)!=NULL) { + mi_segment_t* segment = _mi_page_segment(page); + mi_assert_internal(!_mi_process_is_initialized || segment->thread_id == mi_page_heap(page)->thread_id || segment->thread_id==0); + if (segment->page_kind != MI_PAGE_HUGE) { + mi_page_queue_t* pq = mi_page_queue_of(page); + mi_assert_internal(mi_page_queue_contains(pq, page)); + mi_assert_internal(pq->block_size==mi_page_block_size(page) || mi_page_block_size(page) > MI_LARGE_OBJ_SIZE_MAX || mi_page_is_in_full(page)); + mi_assert_internal(mi_heap_contains_queue(mi_page_heap(page),pq)); + } + } + return true; +} +#endif + +void _mi_page_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) { + mi_thread_free_t tfree; + mi_thread_free_t tfreex; + mi_delayed_t old_delay; + do { + tfree = mi_atomic_read(&page->xthread_free); // note: must acquire as we can break this loop and not do a CAS + tfreex = mi_tf_set_delayed(tfree, delay); + old_delay = mi_tf_delayed(tfree); + if (mi_unlikely(old_delay == MI_DELAYED_FREEING)) { + mi_atomic_yield(); // delay until outstanding MI_DELAYED_FREEING are done. + // tfree = mi_tf_set_delayed(tfree, MI_NO_DELAYED_FREE); // will cause CAS to busy fail + } + else if (delay == old_delay) { + break; // avoid atomic operation if already equal + } + else if (!override_never && old_delay == MI_NEVER_DELAYED_FREE) { + break; // leave never-delayed flag set + } + } while ((old_delay == MI_DELAYED_FREEING) || + !mi_atomic_cas_weak(&page->xthread_free, tfreex, tfree)); +} + +/* ----------------------------------------------------------- + Page collect the `local_free` and `thread_free` lists +----------------------------------------------------------- */ + +// Collect the local `thread_free` list using an atomic exchange. +// Note: The exchange must be done atomically as this is used right after +// moving to the full list in `mi_page_collect_ex` and we need to +// ensure that there was no race where the page became unfull just before the move. +static void _mi_page_thread_free_collect(mi_page_t* page) +{ + mi_block_t* head; + mi_thread_free_t tfree; + mi_thread_free_t tfreex; + do { + tfree = mi_atomic_read_relaxed(&page->xthread_free); + head = mi_tf_block(tfree); + tfreex = mi_tf_set_block(tfree,NULL); + } while (!mi_atomic_cas_weak(&page->xthread_free, tfreex, tfree)); + + // return if the list is empty + if (head == NULL) return; + + // find the tail -- also to get a proper count (without data races) + uint32_t max_count = page->capacity; // cannot collect more than capacity + uint32_t count = 1; + mi_block_t* tail = head; + mi_block_t* next; + while ((next = mi_block_next(page,tail)) != NULL && count <= max_count) { + count++; + tail = next; + } + // if `count > max_count` there was a memory corruption (possibly infinite list due to double multi-threaded free) + if (count > max_count) { + _mi_error_message(EFAULT, "corrupted thread-free list\n"); + return; // the thread-free items cannot be freed + } + + // and append the current local free list + mi_block_set_next(page,tail, page->local_free); + page->local_free = head; + + // update counts now + page->used -= count; +} + +void _mi_page_free_collect(mi_page_t* page, bool force) { + mi_assert_internal(page!=NULL); + + // collect the thread free list + if (force || mi_page_thread_free(page) != NULL) { // quick test to avoid an atomic operation + _mi_page_thread_free_collect(page); + } + + // and the local free list + if (page->local_free != NULL) { + if (mi_likely(page->free == NULL)) { + // usual case + page->free = page->local_free; + page->local_free = NULL; + page->is_zero = false; + } + else if (force) { + // append -- only on shutdown (force) as this is a linear operation + mi_block_t* tail = page->local_free; + mi_block_t* next; + while ((next = mi_block_next(page, tail)) != NULL) { + tail = next; + } + mi_block_set_next(page, tail, page->free); + page->free = page->local_free; + page->local_free = NULL; + page->is_zero = false; + } + } + + mi_assert_internal(!force || page->local_free == NULL); +} + + + +/* ----------------------------------------------------------- + Page fresh and retire +----------------------------------------------------------- */ + +// called from segments when reclaiming abandoned pages +void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page) { + mi_assert_expensive(mi_page_is_valid_init(page)); + mi_assert_internal(mi_page_heap(page) == heap); + mi_assert_internal(mi_page_thread_free_flag(page) != MI_NEVER_DELAYED_FREE); + mi_assert_internal(_mi_page_segment(page)->page_kind != MI_PAGE_HUGE); + mi_assert_internal(!page->is_reset); + // TODO: push on full queue immediately if it is full? + mi_page_queue_t* pq = mi_page_queue(heap, mi_page_block_size(page)); + mi_page_queue_push(heap, pq, page); + mi_assert_expensive(_mi_page_is_valid(page)); +} + +// allocate a fresh page from a segment +static mi_page_t* mi_page_fresh_alloc(mi_heap_t* heap, mi_page_queue_t* pq, size_t block_size) { + mi_assert_internal(pq==NULL||mi_heap_contains_queue(heap, pq)); + mi_assert_internal(pq==NULL||block_size == pq->block_size); + mi_page_t* page = _mi_segment_page_alloc(heap, block_size, &heap->tld->segments, &heap->tld->os); + if (page == NULL) { + // this may be out-of-memory, or an abandoned page was reclaimed (and in our queue) + return NULL; + } + // a fresh page was found, initialize it + mi_assert_internal(pq==NULL || _mi_page_segment(page)->page_kind != MI_PAGE_HUGE); + mi_page_init(heap, page, block_size, heap->tld); + _mi_stat_increase(&heap->tld->stats.pages, 1); + if (pq!=NULL) mi_page_queue_push(heap, pq, page); // huge pages use pq==NULL + mi_assert_expensive(_mi_page_is_valid(page)); + return page; +} + +// Get a fresh page to use +static mi_page_t* mi_page_fresh(mi_heap_t* heap, mi_page_queue_t* pq) { + mi_assert_internal(mi_heap_contains_queue(heap, pq)); + mi_page_t* page = mi_page_fresh_alloc(heap, pq, pq->block_size); + if (page==NULL) return NULL; + mi_assert_internal(pq->block_size==mi_page_block_size(page)); + mi_assert_internal(pq==mi_page_queue(heap, mi_page_block_size(page))); + return page; +} + +/* ----------------------------------------------------------- + Do any delayed frees + (put there by other threads if they deallocated in a full page) +----------------------------------------------------------- */ +void _mi_heap_delayed_free(mi_heap_t* heap) { + // take over the list (note: no atomic exchange is it is often NULL) + mi_block_t* block; + do { + block = mi_atomic_read_ptr_relaxed(mi_block_t,&heap->thread_delayed_free); + } while (block != NULL && !mi_atomic_cas_ptr_weak(mi_block_t,&heap->thread_delayed_free, NULL, block)); + + // and free them all + while(block != NULL) { + mi_block_t* next = mi_block_nextx(heap,block, heap->keys); + // use internal free instead of regular one to keep stats etc correct + if (!_mi_free_delayed_block(block)) { + // we might already start delayed freeing while another thread has not yet + // reset the delayed_freeing flag; in that case delay it further by reinserting. + mi_block_t* dfree; + do { + dfree = mi_atomic_read_ptr_relaxed(mi_block_t,&heap->thread_delayed_free); + mi_block_set_nextx(heap, block, dfree, heap->keys); + } while (!mi_atomic_cas_ptr_weak(mi_block_t,&heap->thread_delayed_free, block, dfree)); + } + block = next; + } +} + +/* ----------------------------------------------------------- + Unfull, abandon, free and retire +----------------------------------------------------------- */ + +// Move a page from the full list back to a regular list +void _mi_page_unfull(mi_page_t* page) { + mi_assert_internal(page != NULL); + mi_assert_expensive(_mi_page_is_valid(page)); + mi_assert_internal(mi_page_is_in_full(page)); + if (!mi_page_is_in_full(page)) return; + + mi_heap_t* heap = mi_page_heap(page); + mi_page_queue_t* pqfull = &heap->pages[MI_BIN_FULL]; + mi_page_set_in_full(page, false); // to get the right queue + mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page); + mi_page_set_in_full(page, true); + mi_page_queue_enqueue_from(pq, pqfull, page); +} + +static void mi_page_to_full(mi_page_t* page, mi_page_queue_t* pq) { + mi_assert_internal(pq == mi_page_queue_of(page)); + mi_assert_internal(!mi_page_immediate_available(page)); + mi_assert_internal(!mi_page_is_in_full(page)); + + if (mi_page_is_in_full(page)) return; + mi_page_queue_enqueue_from(&mi_page_heap(page)->pages[MI_BIN_FULL], pq, page); + _mi_page_free_collect(page,false); // try to collect right away in case another thread freed just before MI_USE_DELAYED_FREE was set +} + + +// Abandon a page with used blocks at the end of a thread. +// Note: only call if it is ensured that no references exist from +// the `page->heap->thread_delayed_free` into this page. +// Currently only called through `mi_heap_collect_ex` which ensures this. +void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) { + mi_assert_internal(page != NULL); + mi_assert_expensive(_mi_page_is_valid(page)); + mi_assert_internal(pq == mi_page_queue_of(page)); + mi_assert_internal(mi_page_heap(page) != NULL); + + mi_heap_t* pheap = mi_page_heap(page); + + // remove from our page list + mi_segments_tld_t* segments_tld = &pheap->tld->segments; + mi_page_queue_remove(pq, page); + + // page is no longer associated with our heap + mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE); + mi_page_set_heap(page, NULL); + +#if MI_DEBUG>1 + // check there are no references left.. + for (mi_block_t* block = (mi_block_t*)pheap->thread_delayed_free; block != NULL; block = mi_block_nextx(pheap, block, pheap->keys)) { + mi_assert_internal(_mi_ptr_page(block) != page); + } +#endif + + // and abandon it + mi_assert_internal(mi_page_heap(page) == NULL); + _mi_segment_page_abandon(page,segments_tld); +} + + +// Free a page with no more free blocks +void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) { + mi_assert_internal(page != NULL); + mi_assert_expensive(_mi_page_is_valid(page)); + mi_assert_internal(pq == mi_page_queue_of(page)); + mi_assert_internal(mi_page_all_free(page)); + mi_assert_internal(mi_page_thread_free_flag(page)!=MI_DELAYED_FREEING); + + // no more aligned blocks in here + mi_page_set_has_aligned(page, false); + + // remove from the page list + // (no need to do _mi_heap_delayed_free first as all blocks are already free) + mi_segments_tld_t* segments_tld = &mi_page_heap(page)->tld->segments; + mi_page_queue_remove(pq, page); + + // and free it + mi_page_set_heap(page,NULL); + _mi_segment_page_free(page, force, segments_tld); +} + +#define MI_MAX_RETIRE_SIZE MI_LARGE_OBJ_SIZE_MAX +#define MI_RETIRE_CYCLES (8) + +// Retire a page with no more used blocks +// Important to not retire too quickly though as new +// allocations might coming. +// Note: called from `mi_free` and benchmarks often +// trigger this due to freeing everything and then +// allocating again so careful when changing this. +void _mi_page_retire(mi_page_t* page) { + mi_assert_internal(page != NULL); + mi_assert_expensive(_mi_page_is_valid(page)); + mi_assert_internal(mi_page_all_free(page)); + + mi_page_set_has_aligned(page, false); + + // don't retire too often.. + // (or we end up retiring and re-allocating most of the time) + // NOTE: refine this more: we should not retire if this + // is the only page left with free blocks. It is not clear + // how to check this efficiently though... + // for now, we don't retire if it is the only page left of this size class. + mi_page_queue_t* pq = mi_page_queue_of(page); + if (mi_likely(page->xblock_size <= MI_MAX_RETIRE_SIZE && !mi_page_is_in_full(page))) { + if (pq->last==page && pq->first==page) { // the only page in the queue? + mi_stat_counter_increase(_mi_stats_main.page_no_retire,1); + page->retire_expire = (page->xblock_size <= MI_SMALL_OBJ_SIZE_MAX ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4); + mi_heap_t* heap = mi_page_heap(page); + mi_assert_internal(pq >= heap->pages); + const size_t index = pq - heap->pages; + mi_assert_internal(index < MI_BIN_FULL && index < MI_BIN_HUGE); + if (index < heap->page_retired_min) heap->page_retired_min = index; + if (index > heap->page_retired_max) heap->page_retired_max = index; + mi_assert_internal(mi_page_all_free(page)); + return; // dont't free after all + } + } + + _mi_page_free(page, pq, false); +} + +// free retired pages: we don't need to look at the entire queues +// since we only retire pages that are at the head position in a queue. +void _mi_heap_collect_retired(mi_heap_t* heap, bool force) { + size_t min = MI_BIN_FULL; + size_t max = 0; + for(size_t bin = heap->page_retired_min; bin <= heap->page_retired_max; bin++) { + mi_page_queue_t* pq = &heap->pages[bin]; + mi_page_t* page = pq->first; + if (page != NULL && page->retire_expire != 0) { + if (mi_page_all_free(page)) { + page->retire_expire--; + if (force || page->retire_expire == 0) { + _mi_page_free(pq->first, pq, force); + } + else { + // keep retired, update min/max + if (bin < min) min = bin; + if (bin > max) max = bin; + } + } + else { + page->retire_expire = 0; + } + } + } + heap->page_retired_min = min; + heap->page_retired_max = max; +} + + +/* ----------------------------------------------------------- + Initialize the initial free list in a page. + In secure mode we initialize a randomized list by + alternating between slices. +----------------------------------------------------------- */ + +#define MI_MAX_SLICE_SHIFT (6) // at most 64 slices +#define MI_MAX_SLICES (1UL << MI_MAX_SLICE_SHIFT) +#define MI_MIN_SLICES (2) + +static void mi_page_free_list_extend_secure(mi_heap_t* const heap, mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats) { + UNUSED(stats); + #if (MI_SECURE<=2) + mi_assert_internal(page->free == NULL); + mi_assert_internal(page->local_free == NULL); + #endif + mi_assert_internal(page->capacity + extend <= page->reserved); + mi_assert_internal(bsize == mi_page_block_size(page)); + void* const page_area = _mi_page_start(_mi_page_segment(page), page, NULL); + + // initialize a randomized free list + // set up `slice_count` slices to alternate between + size_t shift = MI_MAX_SLICE_SHIFT; + while ((extend >> shift) == 0) { + shift--; + } + const size_t slice_count = (size_t)1U << shift; + const size_t slice_extend = extend / slice_count; + mi_assert_internal(slice_extend >= 1); + mi_block_t* blocks[MI_MAX_SLICES]; // current start of the slice + size_t counts[MI_MAX_SLICES]; // available objects in the slice + for (size_t i = 0; i < slice_count; i++) { + blocks[i] = mi_page_block_at(page, page_area, bsize, page->capacity + i*slice_extend); + counts[i] = slice_extend; + } + counts[slice_count-1] += (extend % slice_count); // final slice holds the modulus too (todo: distribute evenly?) + + // and initialize the free list by randomly threading through them + // set up first element + const uintptr_t r = _mi_heap_random_next(heap); + size_t current = r % slice_count; + counts[current]--; + mi_block_t* const free_start = blocks[current]; + // and iterate through the rest; use `random_shuffle` for performance + uintptr_t rnd = _mi_random_shuffle(r|1); // ensure not 0 + for (size_t i = 1; i < extend; i++) { + // call random_shuffle only every INTPTR_SIZE rounds + const size_t round = i%MI_INTPTR_SIZE; + if (round == 0) rnd = _mi_random_shuffle(rnd); + // select a random next slice index + size_t next = ((rnd >> 8*round) & (slice_count-1)); + while (counts[next]==0) { // ensure it still has space + next++; + if (next==slice_count) next = 0; + } + // and link the current block to it + counts[next]--; + mi_block_t* const block = blocks[current]; + blocks[current] = (mi_block_t*)((uint8_t*)block + bsize); // bump to the following block + mi_block_set_next(page, block, blocks[next]); // and set next; note: we may have `current == next` + current = next; + } + // prepend to the free list (usually NULL) + mi_block_set_next(page, blocks[current], page->free); // end of the list + page->free = free_start; +} + +static mi_decl_noinline void mi_page_free_list_extend( mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats) +{ + UNUSED(stats); + #if (MI_SECURE <= 2) + mi_assert_internal(page->free == NULL); + mi_assert_internal(page->local_free == NULL); + #endif + mi_assert_internal(page->capacity + extend <= page->reserved); + mi_assert_internal(bsize == mi_page_block_size(page)); + void* const page_area = _mi_page_start(_mi_page_segment(page), page, NULL ); + + mi_block_t* const start = mi_page_block_at(page, page_area, bsize, page->capacity); + + // initialize a sequential free list + mi_block_t* const last = mi_page_block_at(page, page_area, bsize, page->capacity + extend - 1); + mi_block_t* block = start; + while(block <= last) { + mi_block_t* next = (mi_block_t*)((uint8_t*)block + bsize); + mi_block_set_next(page,block,next); + block = next; + } + // prepend to free list (usually `NULL`) + mi_block_set_next(page, last, page->free); + page->free = start; +} + +/* ----------------------------------------------------------- + Page initialize and extend the capacity +----------------------------------------------------------- */ + +#define MI_MAX_EXTEND_SIZE (4*1024) // heuristic, one OS page seems to work well. +#if (MI_SECURE>0) +#define MI_MIN_EXTEND (8*MI_SECURE) // extend at least by this many +#else +#define MI_MIN_EXTEND (1) +#endif + +// Extend the capacity (up to reserved) by initializing a free list +// We do at most `MI_MAX_EXTEND` to avoid touching too much memory +// Note: we also experimented with "bump" allocation on the first +// allocations but this did not speed up any benchmark (due to an +// extra test in malloc? or cache effects?) +static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld) { + mi_assert_expensive(mi_page_is_valid_init(page)); + #if (MI_SECURE<=2) + mi_assert(page->free == NULL); + mi_assert(page->local_free == NULL); + if (page->free != NULL) return; + #endif + if (page->capacity >= page->reserved) return; + + size_t page_size; + //uint8_t* page_start = + _mi_page_start(_mi_page_segment(page), page, &page_size); + mi_stat_counter_increase(tld->stats.pages_extended, 1); + + // calculate the extend count + const size_t bsize = (page->xblock_size < MI_HUGE_BLOCK_SIZE ? page->xblock_size : page_size); + size_t extend = page->reserved - page->capacity; + size_t max_extend = (bsize >= MI_MAX_EXTEND_SIZE ? MI_MIN_EXTEND : MI_MAX_EXTEND_SIZE/(uint32_t)bsize); + if (max_extend < MI_MIN_EXTEND) max_extend = MI_MIN_EXTEND; + + if (extend > max_extend) { + // ensure we don't touch memory beyond the page to reduce page commit. + // the `lean` benchmark tests this. Going from 1 to 8 increases rss by 50%. + extend = (max_extend==0 ? 1 : max_extend); + } + + mi_assert_internal(extend > 0 && extend + page->capacity <= page->reserved); + mi_assert_internal(extend < (1UL<<16)); + + // and append the extend the free list + if (extend < MI_MIN_SLICES || MI_SECURE==0) { //!mi_option_is_enabled(mi_option_secure)) { + mi_page_free_list_extend(page, bsize, extend, &tld->stats ); + } + else { + mi_page_free_list_extend_secure(heap, page, bsize, extend, &tld->stats); + } + // enable the new free list + page->capacity += (uint16_t)extend; + mi_stat_increase(tld->stats.page_committed, extend * bsize); + + // extension into zero initialized memory preserves the zero'd free list + if (!page->is_zero_init) { + page->is_zero = false; + } + mi_assert_expensive(mi_page_is_valid_init(page)); +} + +// Initialize a fresh page +static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi_tld_t* tld) { + mi_assert(page != NULL); + mi_segment_t* segment = _mi_page_segment(page); + mi_assert(segment != NULL); + mi_assert_internal(block_size > 0); + // set fields + mi_page_set_heap(page, heap); + size_t page_size; + _mi_segment_page_start(segment, page, block_size, &page_size, NULL); + page->xblock_size = (block_size < MI_HUGE_BLOCK_SIZE ? (uint32_t)block_size : MI_HUGE_BLOCK_SIZE); + mi_assert_internal(page_size / block_size < (1L<<16)); + page->reserved = (uint16_t)(page_size / block_size); + #ifdef MI_ENCODE_FREELIST + page->keys[0] = _mi_heap_random_next(heap); + page->keys[1] = _mi_heap_random_next(heap); + #endif + page->is_zero = page->is_zero_init; + + mi_assert_internal(page->capacity == 0); + mi_assert_internal(page->free == NULL); + mi_assert_internal(page->used == 0); + mi_assert_internal(page->xthread_free == 0); + mi_assert_internal(page->next == NULL); + mi_assert_internal(page->prev == NULL); + mi_assert_internal(page->retire_expire == 0); + mi_assert_internal(!mi_page_has_aligned(page)); + #if (MI_ENCODE_FREELIST) + mi_assert_internal(page->keys[0] != 0); + mi_assert_internal(page->keys[1] != 0); + #endif + mi_assert_expensive(mi_page_is_valid_init(page)); + + // initialize an initial free list + mi_page_extend_free(heap,page,tld); + mi_assert(mi_page_immediate_available(page)); +} + + +/* ----------------------------------------------------------- + Find pages with free blocks +-------------------------------------------------------------*/ + +// Find a page with free blocks of `page->block_size`. +static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* pq, bool first_try) +{ + // search through the pages in "next fit" order + size_t count = 0; + mi_page_t* page = pq->first; + while (page != NULL) + { + mi_page_t* next = page->next; // remember next + count++; + + // 0. collect freed blocks by us and other threads + _mi_page_free_collect(page, false); + + // 1. if the page contains free blocks, we are done + if (mi_page_immediate_available(page)) { + break; // pick this one + } + + // 2. Try to extend + if (page->capacity < page->reserved) { + mi_page_extend_free(heap, page, heap->tld); + mi_assert_internal(mi_page_immediate_available(page)); + break; + } + + // 3. If the page is completely full, move it to the `mi_pages_full` + // queue so we don't visit long-lived pages too often. + mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page)); + mi_page_to_full(page, pq); + + page = next; + } // for each page + + mi_stat_counter_increase(heap->tld->stats.searches, count); + + if (page == NULL) { + _mi_heap_collect_retired(heap, false); // perhaps make a page available + page = mi_page_fresh(heap, pq); + if (page == NULL && first_try) { + // out-of-memory _or_ an abandoned page with free blocks was reclaimed, try once again + page = mi_page_queue_find_free_ex(heap, pq, false); + } + } + else { + mi_assert(pq->first == page); + page->retire_expire = 0; + } + mi_assert_internal(page == NULL || mi_page_immediate_available(page)); + return page; +} + + + +// Find a page with free blocks of `size`. +static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) { + mi_page_queue_t* pq = mi_page_queue(heap,size); + mi_page_t* page = pq->first; + if (page != NULL) { + if ((MI_SECURE >= 3) && page->capacity < page->reserved && ((_mi_heap_random_next(heap) & 1) == 1)) { + // in secure mode, we extend half the time to increase randomness + mi_page_extend_free(heap, page, heap->tld); + mi_assert_internal(mi_page_immediate_available(page)); + } + else { + _mi_page_free_collect(page,false); + } + if (mi_page_immediate_available(page)) { + page->retire_expire = 0; + return page; // fast path + } + } + return mi_page_queue_find_free_ex(heap, pq, true); +} + + +/* ----------------------------------------------------------- + Users can register a deferred free function called + when the `free` list is empty. Since the `local_free` + is separate this is deterministically called after + a certain number of allocations. +----------------------------------------------------------- */ + +static mi_deferred_free_fun* volatile deferred_free = NULL; +static volatile _Atomic(void*) deferred_arg; // = NULL + +void _mi_deferred_free(mi_heap_t* heap, bool force) { + heap->tld->heartbeat++; + if (deferred_free != NULL && !heap->tld->recurse) { + heap->tld->recurse = true; + deferred_free(force, heap->tld->heartbeat, mi_atomic_read_ptr_relaxed(void,&deferred_arg)); + heap->tld->recurse = false; + } +} + +void mi_register_deferred_free(mi_deferred_free_fun* fn, void* arg) mi_attr_noexcept { + deferred_free = fn; + mi_atomic_write_ptr(void,&deferred_arg, arg); +} + + +/* ----------------------------------------------------------- + General allocation +----------------------------------------------------------- */ + +// A huge page is allocated directly without being in a queue. +// Because huge pages contain just one block, and the segment contains +// just that page, we always treat them as abandoned and any thread +// that frees the block can free the whole page and segment directly. +static mi_page_t* mi_huge_page_alloc(mi_heap_t* heap, size_t size) { + size_t block_size = _mi_os_good_alloc_size(size); + mi_assert_internal(_mi_bin(block_size) == MI_BIN_HUGE); + mi_page_t* page = mi_page_fresh_alloc(heap,NULL,block_size); + if (page != NULL) { + const size_t bsize = mi_page_block_size(page); // note: not `mi_page_usable_block_size` as `size` includes padding already + mi_assert_internal(bsize >= size); + mi_assert_internal(mi_page_immediate_available(page)); + mi_assert_internal(_mi_page_segment(page)->page_kind==MI_PAGE_HUGE); + mi_assert_internal(_mi_page_segment(page)->used==1); + mi_assert_internal(_mi_page_segment(page)->thread_id==0); // abandoned, not in the huge queue + mi_page_set_heap(page, NULL); + + if (bsize > MI_HUGE_OBJ_SIZE_MAX) { + _mi_stat_increase(&heap->tld->stats.giant, bsize); + _mi_stat_counter_increase(&heap->tld->stats.giant_count, 1); + } + else { + _mi_stat_increase(&heap->tld->stats.huge, bsize); + _mi_stat_counter_increase(&heap->tld->stats.huge_count, 1); + } + } + return page; +} + + +// Allocate a page +// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed. +static mi_page_t* mi_find_page(mi_heap_t* heap, size_t size) mi_attr_noexcept { + // huge allocation? + const size_t req_size = size - MI_PADDING_SIZE; // correct for padding_size in case of an overflow on `size` + if (mi_unlikely(req_size > (MI_LARGE_OBJ_SIZE_MAX - MI_PADDING_SIZE) )) { + if (mi_unlikely(req_size > PTRDIFF_MAX)) { // we don't allocate more than PTRDIFF_MAX (see ) + _mi_error_message(EOVERFLOW, "allocation request is too large (%zu bytes)\n", req_size); + return NULL; + } + else { + return mi_huge_page_alloc(heap,size); + } + } + else { + // otherwise find a page with free blocks in our size segregated queues + mi_assert_internal(size >= MI_PADDING_SIZE); + return mi_find_free_page(heap, size); + } +} + +// Generic allocation routine if the fast path (`alloc.c:mi_page_malloc`) does not succeed. +// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed. +void* _mi_malloc_generic(mi_heap_t* heap, size_t size) mi_attr_noexcept +{ + mi_assert_internal(heap != NULL); + + // initialize if necessary + if (mi_unlikely(!mi_heap_is_initialized(heap))) { + mi_thread_init(); // calls `_mi_heap_init` in turn + heap = mi_get_default_heap(); + if (mi_unlikely(!mi_heap_is_initialized(heap))) { return NULL; } + } + mi_assert_internal(mi_heap_is_initialized(heap)); + + // call potential deferred free routines + _mi_deferred_free(heap, false); + + // free delayed frees from other threads + _mi_heap_delayed_free(heap); + + // find (or allocate) a page of the right size + mi_page_t* page = mi_find_page(heap, size); + if (mi_unlikely(page == NULL)) { // first time out of memory, try to collect and retry the allocation once more + mi_heap_collect(heap, true /* force */); + page = mi_find_page(heap, size); + } + + if (mi_unlikely(page == NULL)) { // out of memory + const size_t req_size = size - MI_PADDING_SIZE; // correct for padding_size in case of an overflow on `size` + _mi_error_message(ENOMEM, "unable to allocate memory (%zu bytes)\n", req_size); + return NULL; + } + + mi_assert_internal(mi_page_immediate_available(page)); + mi_assert_internal(mi_page_block_size(page) >= size); + + // and try again, this time succeeding! (i.e. this should never recurse) + return _mi_page_malloc(heap, page, size); +} diff --git a/extlib/mimalloc/src/random.c b/extlib/mimalloc/src/random.c new file mode 100644 index 0000000..2a96ccf --- /dev/null +++ b/extlib/mimalloc/src/random.c @@ -0,0 +1,328 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2019, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc-internal.h" + +#include // memset + +/* ---------------------------------------------------------------------------- +We use our own PRNG to keep predictable performance of random number generation +and to avoid implementations that use a lock. We only use the OS provided +random source to initialize the initial seeds. Since we do not need ultimate +performance but we do rely on the security (for secret cookies in secure mode) +we use a cryptographically secure generator (chacha20). +-----------------------------------------------------------------------------*/ + +#define MI_CHACHA_ROUNDS (20) // perhaps use 12 for better performance? + + +/* ---------------------------------------------------------------------------- +Chacha20 implementation as the original algorithm with a 64-bit nonce +and counter: https://en.wikipedia.org/wiki/Salsa20 +The input matrix has sixteen 32-bit values: +Position 0 to 3: constant key +Position 4 to 11: the key +Position 12 to 13: the counter. +Position 14 to 15: the nonce. + +The implementation uses regular C code which compiles very well on modern compilers. +(gcc x64 has no register spills, and clang 6+ uses SSE instructions) +-----------------------------------------------------------------------------*/ + +static inline uint32_t rotl(uint32_t x, uint32_t shift) { + return (x << shift) | (x >> (32 - shift)); +} + +static inline void qround(uint32_t x[16], size_t a, size_t b, size_t c, size_t d) { + x[a] += x[b]; x[d] = rotl(x[d] ^ x[a], 16); + x[c] += x[d]; x[b] = rotl(x[b] ^ x[c], 12); + x[a] += x[b]; x[d] = rotl(x[d] ^ x[a], 8); + x[c] += x[d]; x[b] = rotl(x[b] ^ x[c], 7); +} + +static void chacha_block(mi_random_ctx_t* ctx) +{ + // scramble into `x` + uint32_t x[16]; + for (size_t i = 0; i < 16; i++) { + x[i] = ctx->input[i]; + } + for (size_t i = 0; i < MI_CHACHA_ROUNDS; i += 2) { + qround(x, 0, 4, 8, 12); + qround(x, 1, 5, 9, 13); + qround(x, 2, 6, 10, 14); + qround(x, 3, 7, 11, 15); + qround(x, 0, 5, 10, 15); + qround(x, 1, 6, 11, 12); + qround(x, 2, 7, 8, 13); + qround(x, 3, 4, 9, 14); + } + + // add scrambled data to the initial state + for (size_t i = 0; i < 16; i++) { + ctx->output[i] = x[i] + ctx->input[i]; + } + ctx->output_available = 16; + + // increment the counter for the next round + ctx->input[12] += 1; + if (ctx->input[12] == 0) { + ctx->input[13] += 1; + if (ctx->input[13] == 0) { // and keep increasing into the nonce + ctx->input[14] += 1; + } + } +} + +static uint32_t chacha_next32(mi_random_ctx_t* ctx) { + if (ctx->output_available <= 0) { + chacha_block(ctx); + ctx->output_available = 16; // (assign again to suppress static analysis warning) + } + const uint32_t x = ctx->output[16 - ctx->output_available]; + ctx->output[16 - ctx->output_available] = 0; // reset once the data is handed out + ctx->output_available--; + return x; +} + +static inline uint32_t read32(const uint8_t* p, size_t idx32) { + const size_t i = 4*idx32; + return ((uint32_t)p[i+0] | (uint32_t)p[i+1] << 8 | (uint32_t)p[i+2] << 16 | (uint32_t)p[i+3] << 24); +} + +static void chacha_init(mi_random_ctx_t* ctx, const uint8_t key[32], uint64_t nonce) +{ + // since we only use chacha for randomness (and not encryption) we + // do not _need_ to read 32-bit values as little endian but we do anyways + // just for being compatible :-) + memset(ctx, 0, sizeof(*ctx)); + for (size_t i = 0; i < 4; i++) { + const uint8_t* sigma = (uint8_t*)"expand 32-byte k"; + ctx->input[i] = read32(sigma,i); + } + for (size_t i = 0; i < 8; i++) { + ctx->input[i + 4] = read32(key,i); + } + ctx->input[12] = 0; + ctx->input[13] = 0; + ctx->input[14] = (uint32_t)nonce; + ctx->input[15] = (uint32_t)(nonce >> 32); +} + +static void chacha_split(mi_random_ctx_t* ctx, uint64_t nonce, mi_random_ctx_t* ctx_new) { + memset(ctx_new, 0, sizeof(*ctx_new)); + memcpy(ctx_new->input, ctx->input, sizeof(ctx_new->input)); + ctx_new->input[12] = 0; + ctx_new->input[13] = 0; + ctx_new->input[14] = (uint32_t)nonce; + ctx_new->input[15] = (uint32_t)(nonce >> 32); + mi_assert_internal(ctx->input[14] != ctx_new->input[14] || ctx->input[15] != ctx_new->input[15]); // do not reuse nonces! + chacha_block(ctx_new); +} + + +/* ---------------------------------------------------------------------------- +Random interface +-----------------------------------------------------------------------------*/ + +#if MI_DEBUG>1 +static bool mi_random_is_initialized(mi_random_ctx_t* ctx) { + return (ctx != NULL && ctx->input[0] != 0); +} +#endif + +void _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* ctx_new) { + mi_assert_internal(mi_random_is_initialized(ctx)); + mi_assert_internal(ctx != ctx_new); + chacha_split(ctx, (uintptr_t)ctx_new /*nonce*/, ctx_new); +} + +uintptr_t _mi_random_next(mi_random_ctx_t* ctx) { + mi_assert_internal(mi_random_is_initialized(ctx)); + #if MI_INTPTR_SIZE <= 4 + return chacha_next32(ctx); + #elif MI_INTPTR_SIZE == 8 + return (((uintptr_t)chacha_next32(ctx) << 32) | chacha_next32(ctx)); + #else + # error "define mi_random_next for this platform" + #endif +} + + +/* ---------------------------------------------------------------------------- +To initialize a fresh random context we rely on the OS: +- Windows : BCryptGenRandom +- osX,bsd,wasi: arc4random_buf +- Linux : getrandom,/dev/urandom +If we cannot get good randomness, we fall back to weak randomness based on a timer and ASLR. +-----------------------------------------------------------------------------*/ + +#if defined(_WIN32) +#pragma comment (lib,"bcrypt.lib") +#include +static bool os_random_buf(void* buf, size_t buf_len) { + return (BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)buf_len, BCRYPT_USE_SYSTEM_PREFERRED_RNG) >= 0); +} +/* +#define SystemFunction036 NTAPI SystemFunction036 +#include +#undef SystemFunction036 +static bool os_random_buf(void* buf, size_t buf_len) { + RtlGenRandom(buf, (ULONG)buf_len); + return true; +} +*/ +#elif defined(ANDROID) || defined(XP_DARWIN) || defined(__APPLE__) || defined(__DragonFly__) || \ + defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ + defined(__sun) || defined(__wasi__) +#include +static bool os_random_buf(void* buf, size_t buf_len) { + arc4random_buf(buf, buf_len); + return true; +} +#elif defined(__linux__) +#include +#include +#include +#include +#include +#include +static bool os_random_buf(void* buf, size_t buf_len) { + // Modern Linux provides `getrandom` but different distributions either use `sys/random.h` or `linux/random.h` + // and for the latter the actual `getrandom` call is not always defined. + // (see ) + // We therefore use a syscall directly and fall back dynamically to /dev/urandom when needed. +#ifdef SYS_getrandom + #ifndef GRND_NONBLOCK + #define GRND_NONBLOCK (1) + #endif + static volatile _Atomic(uintptr_t) no_getrandom; // = 0 + if (mi_atomic_read(&no_getrandom)==0) { + ssize_t ret = syscall(SYS_getrandom, buf, buf_len, GRND_NONBLOCK); + if (ret >= 0) return (buf_len == (size_t)ret); + if (ret != ENOSYS) return false; + mi_atomic_write(&no_getrandom,1); // don't call again, and fall back to /dev/urandom + } +#endif + int flags = O_RDONLY; + #if defined(O_CLOEXEC) + flags |= O_CLOEXEC; + #endif + int fd = open("/dev/urandom", flags, 0); + if (fd < 0) return false; + size_t count = 0; + while(count < buf_len) { + ssize_t ret = read(fd, (char*)buf + count, buf_len - count); + if (ret<=0) { + if (errno!=EAGAIN && errno!=EINTR) break; + } + else { + count += ret; + } + } + close(fd); + return (count==buf_len); +} +#else +static bool os_random_buf(void* buf, size_t buf_len) { + return false; +} +#endif + +#if defined(_WIN32) +#include +#elif defined(__APPLE__) +#include +#else +#include +#endif + +uintptr_t _os_random_weak(uintptr_t extra_seed) { + uintptr_t x = (uintptr_t)&_os_random_weak ^ extra_seed; // ASLR makes the address random + #if defined(_WIN32) + LARGE_INTEGER pcount; + QueryPerformanceCounter(&pcount); + x ^= (uintptr_t)(pcount.QuadPart); + #elif defined(__APPLE__) + x ^= (uintptr_t)mach_absolute_time(); + #else + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + x ^= (uintptr_t)time.tv_sec; + x ^= (uintptr_t)time.tv_nsec; + #endif + // and do a few randomization steps + uintptr_t max = ((x ^ (x >> 17)) & 0x0F) + 1; + for (uintptr_t i = 0; i < max; i++) { + x = _mi_random_shuffle(x); + } + mi_assert_internal(x != 0); + return x; +} + +void _mi_random_init(mi_random_ctx_t* ctx) { + uint8_t key[32]; + if (!os_random_buf(key, sizeof(key))) { + // if we fail to get random data from the OS, we fall back to a + // weak random source based on the current time + _mi_warning_message("unable to use secure randomness\n"); + uintptr_t x = _os_random_weak(0); + for (size_t i = 0; i < 8; i++) { // key is eight 32-bit words. + x = _mi_random_shuffle(x); + ((uint32_t*)key)[i] = (uint32_t)x; + } + } + chacha_init(ctx, key, (uintptr_t)ctx /*nonce*/ ); +} + +/* -------------------------------------------------------- +test vectors from +----------------------------------------------------------- */ +/* +static bool array_equals(uint32_t* x, uint32_t* y, size_t n) { + for (size_t i = 0; i < n; i++) { + if (x[i] != y[i]) return false; + } + return true; +} +static void chacha_test(void) +{ + uint32_t x[4] = { 0x11111111, 0x01020304, 0x9b8d6f43, 0x01234567 }; + uint32_t x_out[4] = { 0xea2a92f4, 0xcb1cf8ce, 0x4581472e, 0x5881c4bb }; + qround(x, 0, 1, 2, 3); + mi_assert_internal(array_equals(x, x_out, 4)); + + uint32_t y[16] = { + 0x879531e0, 0xc5ecf37d, 0x516461b1, 0xc9a62f8a, + 0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0x2a5f714c, + 0x53372767, 0xb00a5631, 0x974c541a, 0x359e9963, + 0x5c971061, 0x3d631689, 0x2098d9d6, 0x91dbd320 }; + uint32_t y_out[16] = { + 0x879531e0, 0xc5ecf37d, 0xbdb886dc, 0xc9a62f8a, + 0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0xcfacafd2, + 0xe46bea80, 0xb00a5631, 0x974c541a, 0x359e9963, + 0x5c971061, 0xccc07c79, 0x2098d9d6, 0x91dbd320 }; + qround(y, 2, 7, 8, 13); + mi_assert_internal(array_equals(y, y_out, 16)); + + mi_random_ctx_t r = { + { 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, + 0x00000001, 0x09000000, 0x4a000000, 0x00000000 }, + {0}, + 0 + }; + uint32_t r_out[16] = { + 0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3, + 0xc7f4d1c7, 0x0368c033, 0x9aaa2204, 0x4e6cd4c3, + 0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9, + 0xd19c12b5, 0xb94e16de, 0xe883d0cb, 0x4e3c50a2 }; + chacha_block(&r); + mi_assert_internal(array_equals(r.output, r_out, 16)); +} +*/ diff --git a/extlib/mimalloc/src/region.c b/extlib/mimalloc/src/region.c new file mode 100644 index 0000000..ae3a799 --- /dev/null +++ b/extlib/mimalloc/src/region.c @@ -0,0 +1,498 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2019, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* ---------------------------------------------------------------------------- +This implements a layer between the raw OS memory (VirtualAlloc/mmap/sbrk/..) +and the segment and huge object allocation by mimalloc. There may be multiple +implementations of this (one could be the identity going directly to the OS, +another could be a simple cache etc), but the current one uses large "regions". +In contrast to the rest of mimalloc, the "regions" are shared between threads and +need to be accessed using atomic operations. +We need this memory layer between the raw OS calls because of: +1. on `sbrk` like systems (like WebAssembly) we need our own memory maps in order + to reuse memory effectively. +2. It turns out that for large objects, between 1MiB and 32MiB (?), the cost of + an OS allocation/free is still (much) too expensive relative to the accesses + in that object :-( (`malloc-large` tests this). This means we need a cheaper + way to reuse memory. +3. This layer allows for NUMA aware allocation. + +Possible issues: +- (2) can potentially be addressed too with a small cache per thread which is much + simpler. Generally though that requires shrinking of huge pages, and may overuse + memory per thread. (and is not compatible with `sbrk`). +- Since the current regions are per-process, we need atomic operations to + claim blocks which may be contended +- In the worst case, we need to search the whole region map (16KiB for 256GiB) + linearly. At what point will direct OS calls be faster? Is there a way to + do this better without adding too much complexity? +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc-internal.h" +#include "mimalloc-atomic.h" + +#include // memset + +#include "bitmap.inc.c" + +// Internal raw OS interface +size_t _mi_os_large_page_size(); +bool _mi_os_protect(void* addr, size_t size); +bool _mi_os_unprotect(void* addr, size_t size); +bool _mi_os_commit(void* p, size_t size, bool* is_zero, mi_stats_t* stats); +bool _mi_os_decommit(void* p, size_t size, mi_stats_t* stats); +bool _mi_os_reset(void* p, size_t size, mi_stats_t* stats); +bool _mi_os_unreset(void* p, size_t size, bool* is_zero, mi_stats_t* stats); + +// arena.c +void _mi_arena_free(void* p, size_t size, size_t memid, bool all_committed, mi_stats_t* stats); +void* _mi_arena_alloc(size_t size, bool* commit, bool* large, bool* is_zero, size_t* memid, mi_os_tld_t* tld); +void* _mi_arena_alloc_aligned(size_t size, size_t alignment, bool* commit, bool* large, bool* is_zero, size_t* memid, mi_os_tld_t* tld); + + + +// Constants +#if (MI_INTPTR_SIZE==8) +#define MI_HEAP_REGION_MAX_SIZE (256 * GiB) // 64KiB for the region map +#elif (MI_INTPTR_SIZE==4) +#define MI_HEAP_REGION_MAX_SIZE (3 * GiB) // ~ KiB for the region map +#else +#error "define the maximum heap space allowed for regions on this platform" +#endif + +#define MI_SEGMENT_ALIGN MI_SEGMENT_SIZE + +#define MI_REGION_MAX_BLOCKS MI_BITMAP_FIELD_BITS +#define MI_REGION_SIZE (MI_SEGMENT_SIZE * MI_BITMAP_FIELD_BITS) // 256MiB (64MiB on 32 bits) +#define MI_REGION_MAX (MI_HEAP_REGION_MAX_SIZE / MI_REGION_SIZE) // 1024 (48 on 32 bits) +#define MI_REGION_MAX_OBJ_BLOCKS (MI_REGION_MAX_BLOCKS/4) // 64MiB +#define MI_REGION_MAX_OBJ_SIZE (MI_REGION_MAX_OBJ_BLOCKS*MI_SEGMENT_SIZE) + +// Region info +typedef union mi_region_info_u { + uintptr_t value; + struct { + bool valid; // initialized? + bool is_large; // allocated in fixed large/huge OS pages + short numa_node; // the associated NUMA node (where -1 means no associated node) + } x; +} mi_region_info_t; + + +// A region owns a chunk of REGION_SIZE (256MiB) (virtual) memory with +// a bit map with one bit per MI_SEGMENT_SIZE (4MiB) block. +typedef struct mem_region_s { + volatile _Atomic(uintptr_t) info; // mi_region_info_t.value + volatile _Atomic(void*) start; // start of the memory area + mi_bitmap_field_t in_use; // bit per in-use block + mi_bitmap_field_t dirty; // track if non-zero per block + mi_bitmap_field_t commit; // track if committed per block + mi_bitmap_field_t reset; // track if reset per block + volatile _Atomic(uintptr_t) arena_memid; // if allocated from a (huge page) arena + uintptr_t padding; // round to 8 fields +} mem_region_t; + +// The region map +static mem_region_t regions[MI_REGION_MAX]; + +// Allocated regions +static volatile _Atomic(uintptr_t) regions_count; // = 0; + + +/* ---------------------------------------------------------------------------- +Utility functions +-----------------------------------------------------------------------------*/ + +// Blocks (of 4MiB) needed for the given size. +static size_t mi_region_block_count(size_t size) { + return _mi_divide_up(size, MI_SEGMENT_SIZE); +} + +/* +// Return a rounded commit/reset size such that we don't fragment large OS pages into small ones. +static size_t mi_good_commit_size(size_t size) { + if (size > (SIZE_MAX - _mi_os_large_page_size())) return size; + return _mi_align_up(size, _mi_os_large_page_size()); +} +*/ + +// Return if a pointer points into a region reserved by us. +bool mi_is_in_heap_region(const void* p) mi_attr_noexcept { + if (p==NULL) return false; + size_t count = mi_atomic_read_relaxed(®ions_count); + for (size_t i = 0; i < count; i++) { + uint8_t* start = mi_atomic_read_ptr_relaxed(uint8_t,®ions[i].start); + if (start != NULL && (uint8_t*)p >= start && (uint8_t*)p < start + MI_REGION_SIZE) return true; + } + return false; +} + + +static void* mi_region_blocks_start(const mem_region_t* region, mi_bitmap_index_t bit_idx) { + uint8_t* start = mi_atomic_read_ptr(uint8_t,®ion->start); + mi_assert_internal(start != NULL); + return (start + (bit_idx * MI_SEGMENT_SIZE)); +} + +static size_t mi_memid_create(mem_region_t* region, mi_bitmap_index_t bit_idx) { + mi_assert_internal(bit_idx < MI_BITMAP_FIELD_BITS); + size_t idx = region - regions; + mi_assert_internal(®ions[idx] == region); + return (idx*MI_BITMAP_FIELD_BITS + bit_idx)<<1; +} + +static size_t mi_memid_create_from_arena(size_t arena_memid) { + return (arena_memid << 1) | 1; +} + + +static bool mi_memid_is_arena(size_t id, mem_region_t** region, mi_bitmap_index_t* bit_idx, size_t* arena_memid) { + if ((id&1)==1) { + if (arena_memid != NULL) *arena_memid = (id>>1); + return true; + } + else { + size_t idx = (id >> 1) / MI_BITMAP_FIELD_BITS; + *bit_idx = (mi_bitmap_index_t)(id>>1) % MI_BITMAP_FIELD_BITS; + *region = ®ions[idx]; + return false; + } +} + + +/* ---------------------------------------------------------------------------- + Allocate a region is allocated from the OS (or an arena) +-----------------------------------------------------------------------------*/ + +static bool mi_region_try_alloc_os(size_t blocks, bool commit, bool allow_large, mem_region_t** region, mi_bitmap_index_t* bit_idx, mi_os_tld_t* tld) +{ + // not out of regions yet? + if (mi_atomic_read_relaxed(®ions_count) >= MI_REGION_MAX - 1) return false; + + // try to allocate a fresh region from the OS + bool region_commit = (commit && mi_option_is_enabled(mi_option_eager_region_commit)); + bool region_large = (commit && allow_large); + bool is_zero = false; + size_t arena_memid = 0; + void* const start = _mi_arena_alloc_aligned(MI_REGION_SIZE, MI_SEGMENT_ALIGN, ®ion_commit, ®ion_large, &is_zero, &arena_memid, tld); + if (start == NULL) return false; + mi_assert_internal(!(region_large && !allow_large)); + mi_assert_internal(!region_large || region_commit); + + // claim a fresh slot + const uintptr_t idx = mi_atomic_increment(®ions_count); + if (idx >= MI_REGION_MAX) { + mi_atomic_decrement(®ions_count); + _mi_arena_free(start, MI_REGION_SIZE, arena_memid, region_commit, tld->stats); + _mi_warning_message("maximum regions used: %zu GiB (perhaps recompile with a larger setting for MI_HEAP_REGION_MAX_SIZE)", _mi_divide_up(MI_HEAP_REGION_MAX_SIZE, GiB)); + return false; + } + + // allocated, initialize and claim the initial blocks + mem_region_t* r = ®ions[idx]; + r->arena_memid = arena_memid; + mi_atomic_write(&r->in_use, 0); + mi_atomic_write(&r->dirty, (is_zero ? 0 : MI_BITMAP_FIELD_FULL)); + mi_atomic_write(&r->commit, (region_commit ? MI_BITMAP_FIELD_FULL : 0)); + mi_atomic_write(&r->reset, 0); + *bit_idx = 0; + mi_bitmap_claim(&r->in_use, 1, blocks, *bit_idx, NULL); + mi_atomic_write_ptr(uint8_t*,&r->start, start); + + // and share it + mi_region_info_t info; + info.value = 0; // initialize the full union to zero + info.x.valid = true; + info.x.is_large = region_large; + info.x.numa_node = (short)_mi_os_numa_node(tld); + mi_atomic_write(&r->info, info.value); // now make it available to others + *region = r; + return true; +} + +/* ---------------------------------------------------------------------------- + Try to claim blocks in suitable regions +-----------------------------------------------------------------------------*/ + +static bool mi_region_is_suitable(const mem_region_t* region, int numa_node, bool allow_large ) { + // initialized at all? + mi_region_info_t info; + info.value = mi_atomic_read_relaxed(®ion->info); + if (info.value==0) return false; + + // numa correct + if (numa_node >= 0) { // use negative numa node to always succeed + int rnode = info.x.numa_node; + if (rnode >= 0 && rnode != numa_node) return false; + } + + // check allow-large + if (!allow_large && info.x.is_large) return false; + + return true; +} + + +static bool mi_region_try_claim(int numa_node, size_t blocks, bool allow_large, mem_region_t** region, mi_bitmap_index_t* bit_idx, mi_os_tld_t* tld) +{ + // try all regions for a free slot + const size_t count = mi_atomic_read(®ions_count); + size_t idx = tld->region_idx; // Or start at 0 to reuse low addresses? Starting at 0 seems to increase latency though + for (size_t visited = 0; visited < count; visited++, idx++) { + if (idx >= count) idx = 0; // wrap around + mem_region_t* r = ®ions[idx]; + // if this region suits our demand (numa node matches, large OS page matches) + if (mi_region_is_suitable(r, numa_node, allow_large)) { + // then try to atomically claim a segment(s) in this region + if (mi_bitmap_try_find_claim_field(&r->in_use, 0, blocks, bit_idx)) { + tld->region_idx = idx; // remember the last found position + *region = r; + return true; + } + } + } + return false; +} + + +static void* mi_region_try_alloc(size_t blocks, bool* commit, bool* is_large, bool* is_zero, size_t* memid, mi_os_tld_t* tld) +{ + mi_assert_internal(blocks <= MI_BITMAP_FIELD_BITS); + mem_region_t* region; + mi_bitmap_index_t bit_idx; + const int numa_node = (_mi_os_numa_node_count() <= 1 ? -1 : _mi_os_numa_node(tld)); + // try to claim in existing regions + if (!mi_region_try_claim(numa_node, blocks, *is_large, ®ion, &bit_idx, tld)) { + // otherwise try to allocate a fresh region and claim in there + if (!mi_region_try_alloc_os(blocks, *commit, *is_large, ®ion, &bit_idx, tld)) { + // out of regions or memory + return NULL; + } + } + + // ------------------------------------------------ + // found a region and claimed `blocks` at `bit_idx`, initialize them now + mi_assert_internal(region != NULL); + mi_assert_internal(mi_bitmap_is_claimed(®ion->in_use, 1, blocks, bit_idx)); + + mi_region_info_t info; + info.value = mi_atomic_read(®ion->info); + uint8_t* start = mi_atomic_read_ptr(uint8_t,®ion->start); + mi_assert_internal(!(info.x.is_large && !*is_large)); + mi_assert_internal(start != NULL); + + *is_zero = mi_bitmap_claim(®ion->dirty, 1, blocks, bit_idx, NULL); + *is_large = info.x.is_large; + *memid = mi_memid_create(region, bit_idx); + void* p = start + (mi_bitmap_index_bit_in_field(bit_idx) * MI_SEGMENT_SIZE); + + // commit + if (*commit) { + // ensure commit + bool any_uncommitted; + mi_bitmap_claim(®ion->commit, 1, blocks, bit_idx, &any_uncommitted); + if (any_uncommitted) { + mi_assert_internal(!info.x.is_large); + bool commit_zero; + _mi_mem_commit(p, blocks * MI_SEGMENT_SIZE, &commit_zero, tld); + if (commit_zero) *is_zero = true; + } + } + else { + // no need to commit, but check if already fully committed + *commit = mi_bitmap_is_claimed(®ion->commit, 1, blocks, bit_idx); + } + mi_assert_internal(!*commit || mi_bitmap_is_claimed(®ion->commit, 1, blocks, bit_idx)); + + // unreset reset blocks + if (mi_bitmap_is_any_claimed(®ion->reset, 1, blocks, bit_idx)) { + // some blocks are still reset + mi_assert_internal(!info.x.is_large); + mi_assert_internal(!mi_option_is_enabled(mi_option_eager_commit) || *commit || mi_option_get(mi_option_eager_commit_delay) > 0); + mi_bitmap_unclaim(®ion->reset, 1, blocks, bit_idx); + if (*commit || !mi_option_is_enabled(mi_option_reset_decommits)) { // only if needed + bool reset_zero = false; + _mi_mem_unreset(p, blocks * MI_SEGMENT_SIZE, &reset_zero, tld); + if (reset_zero) *is_zero = true; + } + } + mi_assert_internal(!mi_bitmap_is_any_claimed(®ion->reset, 1, blocks, bit_idx)); + + #if (MI_DEBUG>=2) + if (*commit) { ((uint8_t*)p)[0] = 0; } + #endif + + // and return the allocation + mi_assert_internal(p != NULL); + return p; +} + + +/* ---------------------------------------------------------------------------- + Allocation +-----------------------------------------------------------------------------*/ + +// Allocate `size` memory aligned at `alignment`. Return non NULL on success, with a given memory `id`. +// (`id` is abstract, but `id = idx*MI_REGION_MAP_BITS + bitidx`) +void* _mi_mem_alloc_aligned(size_t size, size_t alignment, bool* commit, bool* large, bool* is_zero, size_t* memid, mi_os_tld_t* tld) +{ + mi_assert_internal(memid != NULL && tld != NULL); + mi_assert_internal(size > 0); + *memid = 0; + *is_zero = false; + bool default_large = false; + if (large==NULL) large = &default_large; // ensure `large != NULL` + if (size == 0) return NULL; + size = _mi_align_up(size, _mi_os_page_size()); + + // allocate from regions if possible + void* p = NULL; + size_t arena_memid; + const size_t blocks = mi_region_block_count(size); + if (blocks <= MI_REGION_MAX_OBJ_BLOCKS && alignment <= MI_SEGMENT_ALIGN) { + p = mi_region_try_alloc(blocks, commit, large, is_zero, memid, tld); + if (p == NULL) { + _mi_warning_message("unable to allocate from region: size %zu\n", size); + } + } + if (p == NULL) { + // and otherwise fall back to the OS + p = _mi_arena_alloc_aligned(size, alignment, commit, large, is_zero, &arena_memid, tld); + *memid = mi_memid_create_from_arena(arena_memid); + } + + if (p != NULL) { + mi_assert_internal((uintptr_t)p % alignment == 0); +#if (MI_DEBUG>=2) + if (*commit) { ((uint8_t*)p)[0] = 0; } // ensure the memory is committed +#endif + } + return p; +} + + + +/* ---------------------------------------------------------------------------- +Free +-----------------------------------------------------------------------------*/ + +// Free previously allocated memory with a given id. +void _mi_mem_free(void* p, size_t size, size_t id, bool full_commit, bool any_reset, mi_os_tld_t* tld) { + mi_assert_internal(size > 0 && tld != NULL); + if (p==NULL) return; + if (size==0) return; + size = _mi_align_up(size, _mi_os_page_size()); + + size_t arena_memid = 0; + mi_bitmap_index_t bit_idx; + mem_region_t* region; + if (mi_memid_is_arena(id,®ion,&bit_idx,&arena_memid)) { + // was a direct arena allocation, pass through + _mi_arena_free(p, size, arena_memid, full_commit, tld->stats); + } + else { + // allocated in a region + mi_assert_internal(size <= MI_REGION_MAX_OBJ_SIZE); if (size > MI_REGION_MAX_OBJ_SIZE) return; + const size_t blocks = mi_region_block_count(size); + mi_assert_internal(blocks + bit_idx <= MI_BITMAP_FIELD_BITS); + mi_region_info_t info; + info.value = mi_atomic_read(®ion->info); + mi_assert_internal(info.value != 0); + void* blocks_start = mi_region_blocks_start(region, bit_idx); + mi_assert_internal(blocks_start == p); // not a pointer in our area? + mi_assert_internal(bit_idx + blocks <= MI_BITMAP_FIELD_BITS); + if (blocks_start != p || bit_idx + blocks > MI_BITMAP_FIELD_BITS) return; // or `abort`? + + // committed? + if (full_commit && (size % MI_SEGMENT_SIZE) == 0) { + mi_bitmap_claim(®ion->commit, 1, blocks, bit_idx, NULL); + } + + if (any_reset) { + // set the is_reset bits if any pages were reset + mi_bitmap_claim(®ion->reset, 1, blocks, bit_idx, NULL); + } + + // reset the blocks to reduce the working set. + if (!info.x.is_large && mi_option_is_enabled(mi_option_segment_reset) + && (mi_option_is_enabled(mi_option_eager_commit) || + mi_option_is_enabled(mi_option_reset_decommits))) // cannot reset halfway committed segments, use only `option_page_reset` instead + { + bool any_unreset; + mi_bitmap_claim(®ion->reset, 1, blocks, bit_idx, &any_unreset); + if (any_unreset) { + _mi_abandoned_await_readers(); // ensure no more pending write (in case reset = decommit) + _mi_mem_reset(p, blocks * MI_SEGMENT_SIZE, tld); + } + } + + // and unclaim + bool all_unclaimed = mi_bitmap_unclaim(®ion->in_use, 1, blocks, bit_idx); + mi_assert_internal(all_unclaimed); UNUSED(all_unclaimed); + } +} + + +/* ---------------------------------------------------------------------------- + collection +-----------------------------------------------------------------------------*/ +void _mi_mem_collect(mi_os_tld_t* tld) { + // free every region that has no segments in use. + uintptr_t rcount = mi_atomic_read_relaxed(®ions_count); + for (size_t i = 0; i < rcount; i++) { + mem_region_t* region = ®ions[i]; + if (mi_atomic_read_relaxed(®ion->info) != 0) { + // if no segments used, try to claim the whole region + uintptr_t m; + do { + m = mi_atomic_read_relaxed(®ion->in_use); + } while(m == 0 && !mi_atomic_cas_weak(®ion->in_use, MI_BITMAP_FIELD_FULL, 0 )); + if (m == 0) { + // on success, free the whole region + uint8_t* start = mi_atomic_read_ptr(uint8_t,®ions[i].start); + size_t arena_memid = mi_atomic_read_relaxed(®ions[i].arena_memid); + uintptr_t commit = mi_atomic_read_relaxed(®ions[i].commit); + memset(®ions[i], 0, sizeof(mem_region_t)); + // and release the whole region + mi_atomic_write(®ion->info, 0); + if (start != NULL) { // && !_mi_os_is_huge_reserved(start)) { + _mi_abandoned_await_readers(); // ensure no pending reads + _mi_arena_free(start, MI_REGION_SIZE, arena_memid, (~commit == 0), tld->stats); + } + } + } + } +} + + +/* ---------------------------------------------------------------------------- + Other +-----------------------------------------------------------------------------*/ + +bool _mi_mem_reset(void* p, size_t size, mi_os_tld_t* tld) { + return _mi_os_reset(p, size, tld->stats); +} + +bool _mi_mem_unreset(void* p, size_t size, bool* is_zero, mi_os_tld_t* tld) { + return _mi_os_unreset(p, size, is_zero, tld->stats); +} + +bool _mi_mem_commit(void* p, size_t size, bool* is_zero, mi_os_tld_t* tld) { + return _mi_os_commit(p, size, is_zero, tld->stats); +} + +bool _mi_mem_decommit(void* p, size_t size, mi_os_tld_t* tld) { + return _mi_os_decommit(p, size, tld->stats); +} + +bool _mi_mem_protect(void* p, size_t size) { + return _mi_os_protect(p, size); +} + +bool _mi_mem_unprotect(void* p, size_t size) { + return _mi_os_unprotect(p, size); +} diff --git a/extlib/mimalloc/src/segment.c b/extlib/mimalloc/src/segment.c new file mode 100644 index 0000000..8a5ba8c --- /dev/null +++ b/extlib/mimalloc/src/segment.c @@ -0,0 +1,1343 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc-internal.h" +#include "mimalloc-atomic.h" + +#include // memset +#include + +#define MI_PAGE_HUGE_ALIGN (256*1024) + +static uint8_t* mi_segment_raw_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size); + +/* -------------------------------------------------------------------------------- + Segment allocation + We allocate pages inside bigger "segments" (4mb on 64-bit). This is to avoid + splitting VMA's on Linux and reduce fragmentation on other OS's. + Each thread owns its own segments. + + Currently we have: + - small pages (64kb), 64 in one segment + - medium pages (512kb), 8 in one segment + - large pages (4mb), 1 in one segment + - huge blocks > MI_LARGE_OBJ_SIZE_MAX become large segment with 1 page + + In any case the memory for a segment is virtual and usually committed on demand. + (i.e. we are careful to not touch the memory until we actually allocate a block there) + + If a thread ends, it "abandons" pages with used blocks + and there is an abandoned segment list whose segments can + be reclaimed by still running threads, much like work-stealing. +-------------------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------- + Queue of segments containing free pages +----------------------------------------------------------- */ + +#if (MI_DEBUG>=3) +static bool mi_segment_queue_contains(const mi_segment_queue_t* queue, const mi_segment_t* segment) { + mi_assert_internal(segment != NULL); + mi_segment_t* list = queue->first; + while (list != NULL) { + if (list == segment) break; + mi_assert_internal(list->next==NULL || list->next->prev == list); + mi_assert_internal(list->prev==NULL || list->prev->next == list); + list = list->next; + } + return (list == segment); +} +#endif + +static bool mi_segment_queue_is_empty(const mi_segment_queue_t* queue) { + return (queue->first == NULL); +} + +static void mi_segment_queue_remove(mi_segment_queue_t* queue, mi_segment_t* segment) { + mi_assert_expensive(mi_segment_queue_contains(queue, segment)); + if (segment->prev != NULL) segment->prev->next = segment->next; + if (segment->next != NULL) segment->next->prev = segment->prev; + if (segment == queue->first) queue->first = segment->next; + if (segment == queue->last) queue->last = segment->prev; + segment->next = NULL; + segment->prev = NULL; +} + +static void mi_segment_enqueue(mi_segment_queue_t* queue, mi_segment_t* segment) { + mi_assert_expensive(!mi_segment_queue_contains(queue, segment)); + segment->next = NULL; + segment->prev = queue->last; + if (queue->last != NULL) { + mi_assert_internal(queue->last->next == NULL); + queue->last->next = segment; + queue->last = segment; + } + else { + queue->last = queue->first = segment; + } +} + +static mi_segment_queue_t* mi_segment_free_queue_of_kind(mi_page_kind_t kind, mi_segments_tld_t* tld) { + if (kind == MI_PAGE_SMALL) return &tld->small_free; + else if (kind == MI_PAGE_MEDIUM) return &tld->medium_free; + else return NULL; +} + +static mi_segment_queue_t* mi_segment_free_queue(const mi_segment_t* segment, mi_segments_tld_t* tld) { + return mi_segment_free_queue_of_kind(segment->page_kind, tld); +} + +// remove from free queue if it is in one +static void mi_segment_remove_from_free_queue(mi_segment_t* segment, mi_segments_tld_t* tld) { + mi_segment_queue_t* queue = mi_segment_free_queue(segment, tld); // may be NULL + bool in_queue = (queue!=NULL && (segment->next != NULL || segment->prev != NULL || queue->first == segment)); + if (in_queue) { + mi_segment_queue_remove(queue, segment); + } +} + +static void mi_segment_insert_in_free_queue(mi_segment_t* segment, mi_segments_tld_t* tld) { + mi_segment_enqueue(mi_segment_free_queue(segment, tld), segment); +} + + +/* ----------------------------------------------------------- + Invariant checking +----------------------------------------------------------- */ + +#if (MI_DEBUG>=2) +static bool mi_segment_is_in_free_queue(const mi_segment_t* segment, mi_segments_tld_t* tld) { + mi_segment_queue_t* queue = mi_segment_free_queue(segment, tld); + bool in_queue = (queue!=NULL && (segment->next != NULL || segment->prev != NULL || queue->first == segment)); + if (in_queue) { + mi_assert_expensive(mi_segment_queue_contains(queue, segment)); + } + return in_queue; +} +#endif + +static size_t mi_segment_page_size(const mi_segment_t* segment) { + if (segment->capacity > 1) { + mi_assert_internal(segment->page_kind <= MI_PAGE_MEDIUM); + return ((size_t)1 << segment->page_shift); + } + else { + mi_assert_internal(segment->page_kind >= MI_PAGE_LARGE); + return segment->segment_size; + } +} + + +#if (MI_DEBUG>=2) +static bool mi_pages_reset_contains(const mi_page_t* page, mi_segments_tld_t* tld) { + mi_page_t* p = tld->pages_reset.first; + while (p != NULL) { + if (p == page) return true; + p = p->next; + } + return false; +} +#endif + +#if (MI_DEBUG>=3) +static bool mi_segment_is_valid(const mi_segment_t* segment, mi_segments_tld_t* tld) { + mi_assert_internal(segment != NULL); + mi_assert_internal(_mi_ptr_cookie(segment) == segment->cookie); + mi_assert_internal(segment->used <= segment->capacity); + mi_assert_internal(segment->abandoned <= segment->used); + size_t nfree = 0; + for (size_t i = 0; i < segment->capacity; i++) { + const mi_page_t* const page = &segment->pages[i]; + if (!page->segment_in_use) { + nfree++; + } + if (page->segment_in_use || page->is_reset) { + mi_assert_expensive(!mi_pages_reset_contains(page, tld)); + } + } + mi_assert_internal(nfree + segment->used == segment->capacity); + // mi_assert_internal(segment->thread_id == _mi_thread_id() || (segment->thread_id==0)); // or 0 + mi_assert_internal(segment->page_kind == MI_PAGE_HUGE || + (mi_segment_page_size(segment) * segment->capacity == segment->segment_size)); + return true; +} +#endif + +static bool mi_page_not_in_queue(const mi_page_t* page, mi_segments_tld_t* tld) { + mi_assert_internal(page != NULL); + if (page->next != NULL || page->prev != NULL) { + mi_assert_internal(mi_pages_reset_contains(page, tld)); + return false; + } + else { + // both next and prev are NULL, check for singleton list + return (tld->pages_reset.first != page && tld->pages_reset.last != page); + } +} + + +/* ----------------------------------------------------------- + Guard pages +----------------------------------------------------------- */ + +static void mi_segment_protect_range(void* p, size_t size, bool protect) { + if (protect) { + _mi_mem_protect(p, size); + } + else { + _mi_mem_unprotect(p, size); + } +} + +static void mi_segment_protect(mi_segment_t* segment, bool protect, mi_os_tld_t* tld) { + // add/remove guard pages + if (MI_SECURE != 0) { + // in secure mode, we set up a protected page in between the segment info and the page data + const size_t os_page_size = _mi_os_page_size(); + mi_assert_internal((segment->segment_info_size - os_page_size) >= (sizeof(mi_segment_t) + ((segment->capacity - 1) * sizeof(mi_page_t)))); + mi_assert_internal(((uintptr_t)segment + segment->segment_info_size) % os_page_size == 0); + mi_segment_protect_range((uint8_t*)segment + segment->segment_info_size - os_page_size, os_page_size, protect); + if (MI_SECURE <= 1 || segment->capacity == 1) { + // and protect the last (or only) page too + mi_assert_internal(MI_SECURE <= 1 || segment->page_kind >= MI_PAGE_LARGE); + uint8_t* start = (uint8_t*)segment + segment->segment_size - os_page_size; + if (protect && !segment->mem_is_committed) { + // ensure secure page is committed + _mi_mem_commit(start, os_page_size, NULL, tld); + } + mi_segment_protect_range(start, os_page_size, protect); + } + else { + // or protect every page + const size_t page_size = mi_segment_page_size(segment); + for (size_t i = 0; i < segment->capacity; i++) { + if (segment->pages[i].is_committed) { + mi_segment_protect_range((uint8_t*)segment + (i+1)*page_size - os_page_size, os_page_size, protect); + } + } + } + } +} + +/* ----------------------------------------------------------- + Page reset +----------------------------------------------------------- */ + +static void mi_page_reset(mi_segment_t* segment, mi_page_t* page, size_t size, mi_segments_tld_t* tld) { + mi_assert_internal(page->is_committed); + if (!mi_option_is_enabled(mi_option_page_reset)) return; + if (segment->mem_is_fixed || page->segment_in_use || !page->is_committed || page->is_reset) return; + size_t psize; + void* start = mi_segment_raw_page_start(segment, page, &psize); + page->is_reset = true; + mi_assert_internal(size <= psize); + size_t reset_size = ((size == 0 || size > psize) ? psize : size); + if (reset_size > 0) _mi_mem_reset(start, reset_size, tld->os); +} + +static bool mi_page_unreset(mi_segment_t* segment, mi_page_t* page, size_t size, mi_segments_tld_t* tld) +{ + mi_assert_internal(page->is_reset); + mi_assert_internal(page->is_committed); + mi_assert_internal(!segment->mem_is_fixed); + if (segment->mem_is_fixed || !page->is_committed || !page->is_reset) return true; + page->is_reset = false; + size_t psize; + uint8_t* start = mi_segment_raw_page_start(segment, page, &psize); + size_t unreset_size = (size == 0 || size > psize ? psize : size); + bool is_zero = false; + bool ok = true; + if (unreset_size > 0) { + ok = _mi_mem_unreset(start, unreset_size, &is_zero, tld->os); + } + if (is_zero) page->is_zero_init = true; + return ok; +} + + +/* ----------------------------------------------------------- + The free page queue +----------------------------------------------------------- */ + +// we re-use the `used` field for the expiration counter. Since this is a +// a 32-bit field while the clock is always 64-bit we need to guard +// against overflow, we use substraction to check for expiry which work +// as long as the reset delay is under (2^30 - 1) milliseconds (~12 days) +static void mi_page_reset_set_expire(mi_page_t* page) { + uint32_t expire = (uint32_t)_mi_clock_now() + mi_option_get(mi_option_reset_delay); + page->used = expire; +} + +static bool mi_page_reset_is_expired(mi_page_t* page, mi_msecs_t now) { + int32_t expire = (int32_t)(page->used); + return (((int32_t)now - expire) >= 0); +} + +static void mi_pages_reset_add(mi_segment_t* segment, mi_page_t* page, mi_segments_tld_t* tld) { + mi_assert_internal(!page->segment_in_use || !page->is_committed); + mi_assert_internal(mi_page_not_in_queue(page,tld)); + mi_assert_expensive(!mi_pages_reset_contains(page, tld)); + mi_assert_internal(_mi_page_segment(page)==segment); + if (!mi_option_is_enabled(mi_option_page_reset)) return; + if (segment->mem_is_fixed || page->segment_in_use || !page->is_committed || page->is_reset) return; + + if (mi_option_get(mi_option_reset_delay) == 0) { + // reset immediately? + mi_page_reset(segment, page, 0, tld); + } + else { + // otherwise push on the delayed page reset queue + mi_page_queue_t* pq = &tld->pages_reset; + // push on top + mi_page_reset_set_expire(page); + page->next = pq->first; + page->prev = NULL; + if (pq->first == NULL) { + mi_assert_internal(pq->last == NULL); + pq->first = pq->last = page; + } + else { + pq->first->prev = page; + pq->first = page; + } + } +} + +static void mi_pages_reset_remove(mi_page_t* page, mi_segments_tld_t* tld) { + if (mi_page_not_in_queue(page,tld)) return; + + mi_page_queue_t* pq = &tld->pages_reset; + mi_assert_internal(pq!=NULL); + mi_assert_internal(!page->segment_in_use); + mi_assert_internal(mi_pages_reset_contains(page, tld)); + if (page->prev != NULL) page->prev->next = page->next; + if (page->next != NULL) page->next->prev = page->prev; + if (page == pq->last) pq->last = page->prev; + if (page == pq->first) pq->first = page->next; + page->next = page->prev = NULL; + page->used = 0; +} + +static void mi_pages_reset_remove_all_in_segment(mi_segment_t* segment, bool force_reset, mi_segments_tld_t* tld) { + if (segment->mem_is_fixed) return; // never reset in huge OS pages + for (size_t i = 0; i < segment->capacity; i++) { + mi_page_t* page = &segment->pages[i]; + if (!page->segment_in_use && page->is_committed && !page->is_reset) { + mi_pages_reset_remove(page, tld); + if (force_reset) { + mi_page_reset(segment, page, 0, tld); + } + } + else { + mi_assert_internal(mi_page_not_in_queue(page,tld)); + } + } +} + +static void mi_reset_delayed(mi_segments_tld_t* tld) { + if (!mi_option_is_enabled(mi_option_page_reset)) return; + mi_msecs_t now = _mi_clock_now(); + mi_page_queue_t* pq = &tld->pages_reset; + // from oldest up to the first that has not expired yet + mi_page_t* page = pq->last; + while (page != NULL && mi_page_reset_is_expired(page,now)) { + mi_page_t* const prev = page->prev; // save previous field + mi_page_reset(_mi_page_segment(page), page, 0, tld); + page->used = 0; + page->prev = page->next = NULL; + page = prev; + } + // discard the reset pages from the queue + pq->last = page; + if (page != NULL){ + page->next = NULL; + } + else { + pq->first = NULL; + } +} + + +/* ----------------------------------------------------------- + Segment size calculations +----------------------------------------------------------- */ + +// Raw start of the page available memory; can be used on uninitialized pages (only `segment_idx` must be set) +// The raw start is not taking aligned block allocation into consideration. +static uint8_t* mi_segment_raw_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size) { + size_t psize = (segment->page_kind == MI_PAGE_HUGE ? segment->segment_size : (size_t)1 << segment->page_shift); + uint8_t* p = (uint8_t*)segment + page->segment_idx * psize; + + if (page->segment_idx == 0) { + // the first page starts after the segment info (and possible guard page) + p += segment->segment_info_size; + psize -= segment->segment_info_size; + } + + if (MI_SECURE > 1 || (MI_SECURE == 1 && page->segment_idx == segment->capacity - 1)) { + // secure == 1: the last page has an os guard page at the end + // secure > 1: every page has an os guard page + psize -= _mi_os_page_size(); + } + + if (page_size != NULL) *page_size = psize; + mi_assert_internal(page->xblock_size == 0 || _mi_ptr_page(p) == page); + mi_assert_internal(_mi_ptr_segment(p) == segment); + return p; +} + +// Start of the page available memory; can be used on uninitialized pages (only `segment_idx` must be set) +uint8_t* _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t block_size, size_t* page_size, size_t* pre_size) +{ + size_t psize; + uint8_t* p = mi_segment_raw_page_start(segment, page, &psize); + if (pre_size != NULL) *pre_size = 0; + if (page->segment_idx == 0 && block_size > 0 && segment->page_kind <= MI_PAGE_MEDIUM) { + // for small and medium objects, ensure the page start is aligned with the block size (PR#66 by kickunderscore) + size_t adjust = block_size - ((uintptr_t)p % block_size); + if (adjust < block_size) { + p += adjust; + psize -= adjust; + if (pre_size != NULL) *pre_size = adjust; + } + mi_assert_internal((uintptr_t)p % block_size == 0); + } + + if (page_size != NULL) *page_size = psize; + mi_assert_internal(page->xblock_size==0 || _mi_ptr_page(p) == page); + mi_assert_internal(_mi_ptr_segment(p) == segment); + return p; +} + +static size_t mi_segment_size(size_t capacity, size_t required, size_t* pre_size, size_t* info_size) +{ + const size_t minsize = sizeof(mi_segment_t) + ((capacity - 1) * sizeof(mi_page_t)) + 16 /* padding */; + size_t guardsize = 0; + size_t isize = 0; + + if (MI_SECURE == 0) { + // normally no guard pages + isize = _mi_align_up(minsize, 16 * MI_MAX_ALIGN_SIZE); + } + else { + // in secure mode, we set up a protected page in between the segment info + // and the page data (and one at the end of the segment) + const size_t page_size = _mi_os_page_size(); + isize = _mi_align_up(minsize, page_size); + guardsize = page_size; + required = _mi_align_up(required, page_size); + } + + if (info_size != NULL) *info_size = isize; + if (pre_size != NULL) *pre_size = isize + guardsize; + return (required==0 ? MI_SEGMENT_SIZE : _mi_align_up( required + isize + 2*guardsize, MI_PAGE_HUGE_ALIGN) ); +} + + +/* ---------------------------------------------------------------------------- +Segment caches +We keep a small segment cache per thread to increase local +reuse and avoid setting/clearing guard pages in secure mode. +------------------------------------------------------------------------------- */ + +static void mi_segments_track_size(long segment_size, mi_segments_tld_t* tld) { + if (segment_size>=0) _mi_stat_increase(&tld->stats->segments,1); + else _mi_stat_decrease(&tld->stats->segments,1); + tld->count += (segment_size >= 0 ? 1 : -1); + if (tld->count > tld->peak_count) tld->peak_count = tld->count; + tld->current_size += segment_size; + if (tld->current_size > tld->peak_size) tld->peak_size = tld->current_size; +} + +static void mi_segment_os_free(mi_segment_t* segment, size_t segment_size, mi_segments_tld_t* tld) { + segment->thread_id = 0; + mi_segments_track_size(-((long)segment_size),tld); + if (MI_SECURE != 0) { + mi_assert_internal(!segment->mem_is_fixed); + mi_segment_protect(segment, false, tld->os); // ensure no more guard pages are set + } + + bool any_reset = false; + bool fully_committed = true; + for (size_t i = 0; i < segment->capacity; i++) { + mi_page_t* page = &segment->pages[i]; + if (!page->is_committed) { fully_committed = false; } + if (page->is_reset) { any_reset = true; } + } + if (any_reset && mi_option_is_enabled(mi_option_reset_decommits)) { + fully_committed = false; + } + + _mi_mem_free(segment, segment_size, segment->memid, fully_committed, any_reset, tld->os); +} + + +// The thread local segment cache is limited to be at most 1/8 of the peak size of segments in use, +#define MI_SEGMENT_CACHE_FRACTION (8) + +// note: returned segment may be partially reset +static mi_segment_t* mi_segment_cache_pop(size_t segment_size, mi_segments_tld_t* tld) { + if (segment_size != 0 && segment_size != MI_SEGMENT_SIZE) return NULL; + mi_segment_t* segment = tld->cache; + if (segment == NULL) return NULL; + tld->cache_count--; + tld->cache = segment->next; + segment->next = NULL; + mi_assert_internal(segment->segment_size == MI_SEGMENT_SIZE); + _mi_stat_decrease(&tld->stats->segments_cache, 1); + return segment; +} + +static bool mi_segment_cache_full(mi_segments_tld_t* tld) +{ + // if (tld->count == 1 && tld->cache_count==0) return false; // always cache at least the final segment of a thread + size_t max_cache = mi_option_get(mi_option_segment_cache); + if (tld->cache_count < max_cache + && tld->cache_count < (1 + (tld->peak_count / MI_SEGMENT_CACHE_FRACTION)) // at least allow a 1 element cache + ) { + return false; + } + // take the opportunity to reduce the segment cache if it is too large (now) + // TODO: this never happens as we check against peak usage, should we use current usage instead? + while (tld->cache_count > max_cache) { //(1 + (tld->peak_count / MI_SEGMENT_CACHE_FRACTION))) { + mi_segment_t* segment = mi_segment_cache_pop(0,tld); + mi_assert_internal(segment != NULL); + if (segment != NULL) mi_segment_os_free(segment, segment->segment_size, tld); + } + return true; +} + +static bool mi_segment_cache_push(mi_segment_t* segment, mi_segments_tld_t* tld) { + mi_assert_internal(!mi_segment_is_in_free_queue(segment, tld)); + mi_assert_internal(segment->next == NULL); + if (segment->segment_size != MI_SEGMENT_SIZE || mi_segment_cache_full(tld)) { + return false; + } + mi_assert_internal(segment->segment_size == MI_SEGMENT_SIZE); + segment->next = tld->cache; + tld->cache = segment; + tld->cache_count++; + _mi_stat_increase(&tld->stats->segments_cache,1); + return true; +} + +// called by threads that are terminating to free cached segments +void _mi_segment_thread_collect(mi_segments_tld_t* tld) { + mi_segment_t* segment; + while ((segment = mi_segment_cache_pop(0,tld)) != NULL) { + mi_segment_os_free(segment, segment->segment_size, tld); + } + mi_assert_internal(tld->cache_count == 0); + mi_assert_internal(tld->cache == NULL); +#if MI_DEBUG>=2 + if (!_mi_is_main_thread()) { + mi_assert_internal(tld->pages_reset.first == NULL); + mi_assert_internal(tld->pages_reset.last == NULL); + } +#endif +} + + +/* ----------------------------------------------------------- + Segment allocation +----------------------------------------------------------- */ + +// Allocate a segment from the OS aligned to `MI_SEGMENT_SIZE` . +static mi_segment_t* mi_segment_init(mi_segment_t* segment, size_t required, mi_page_kind_t page_kind, size_t page_shift, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) +{ + // the segment parameter is non-null if it came from our cache + mi_assert_internal(segment==NULL || (required==0 && page_kind <= MI_PAGE_LARGE)); + + // calculate needed sizes first + size_t capacity; + if (page_kind == MI_PAGE_HUGE) { + mi_assert_internal(page_shift == MI_SEGMENT_SHIFT && required > 0); + capacity = 1; + } + else { + mi_assert_internal(required == 0); + size_t page_size = (size_t)1 << page_shift; + capacity = MI_SEGMENT_SIZE / page_size; + mi_assert_internal(MI_SEGMENT_SIZE % page_size == 0); + mi_assert_internal(capacity >= 1 && capacity <= MI_SMALL_PAGES_PER_SEGMENT); + } + size_t info_size; + size_t pre_size; + size_t segment_size = mi_segment_size(capacity, required, &pre_size, &info_size); + mi_assert_internal(segment_size >= required); + + // Initialize parameters + const bool eager_delayed = (page_kind <= MI_PAGE_MEDIUM && tld->count < (size_t)mi_option_get(mi_option_eager_commit_delay)); + const bool eager = !eager_delayed && mi_option_is_enabled(mi_option_eager_commit); + bool commit = eager; // || (page_kind >= MI_PAGE_LARGE); + bool pages_still_good = false; + bool is_zero = false; + + // Try to get it from our thread local cache first + if (segment != NULL) { + // came from cache + mi_assert_internal(segment->segment_size == segment_size); + if (page_kind <= MI_PAGE_MEDIUM && segment->page_kind == page_kind && segment->segment_size == segment_size) { + pages_still_good = true; + } + else + { + if (MI_SECURE!=0) { + mi_assert_internal(!segment->mem_is_fixed); + mi_segment_protect(segment, false, tld->os); // reset protection if the page kind differs + } + // different page kinds; unreset any reset pages, and unprotect + // TODO: optimize cache pop to return fitting pages if possible? + for (size_t i = 0; i < segment->capacity; i++) { + mi_page_t* page = &segment->pages[i]; + if (page->is_reset) { + if (!commit && mi_option_is_enabled(mi_option_reset_decommits)) { + page->is_reset = false; + } + else { + mi_page_unreset(segment, page, 0, tld); // todo: only unreset the part that was reset? (instead of the full page) + } + } + } + // ensure the initial info is committed + if (segment->capacity < capacity) { + bool commit_zero = false; + _mi_mem_commit(segment, pre_size, &commit_zero, tld->os); + if (commit_zero) is_zero = true; + } + } + } + else { + // Allocate the segment from the OS + size_t memid; + bool mem_large = (!eager_delayed && (MI_SECURE==0)); // only allow large OS pages once we are no longer lazy + segment = (mi_segment_t*)_mi_mem_alloc_aligned(segment_size, MI_SEGMENT_SIZE, &commit, &mem_large, &is_zero, &memid, os_tld); + if (segment == NULL) return NULL; // failed to allocate + if (!commit) { + // ensure the initial info is committed + bool commit_zero = false; + bool ok = _mi_mem_commit(segment, pre_size, &commit_zero, tld->os); + if (commit_zero) is_zero = true; + if (!ok) { + // commit failed; we cannot touch the memory: free the segment directly and return `NULL` + _mi_mem_free(segment, MI_SEGMENT_SIZE, memid, false, false, os_tld); + return NULL; + } + } + segment->memid = memid; + segment->mem_is_fixed = mem_large; + segment->mem_is_committed = commit; + mi_segments_track_size((long)segment_size, tld); + } + mi_assert_internal(segment != NULL && (uintptr_t)segment % MI_SEGMENT_SIZE == 0); + mi_assert_internal(segment->mem_is_fixed ? segment->mem_is_committed : true); + if (!pages_still_good) { + // zero the segment info (but not the `mem` fields) + ptrdiff_t ofs = offsetof(mi_segment_t, next); + memset((uint8_t*)segment + ofs, 0, info_size - ofs); + + // initialize pages info + for (uint8_t i = 0; i < capacity; i++) { + segment->pages[i].segment_idx = i; + segment->pages[i].is_reset = false; + segment->pages[i].is_committed = commit; + segment->pages[i].is_zero_init = is_zero; + } + } + else { + // zero the segment info but not the pages info (and mem fields) + ptrdiff_t ofs = offsetof(mi_segment_t, next); + memset((uint8_t*)segment + ofs, 0, offsetof(mi_segment_t,pages) - ofs); + } + + // initialize + segment->page_kind = page_kind; + segment->capacity = capacity; + segment->page_shift = page_shift; + segment->segment_size = segment_size; + segment->segment_info_size = pre_size; + segment->thread_id = _mi_thread_id(); + segment->cookie = _mi_ptr_cookie(segment); + // _mi_stat_increase(&tld->stats->page_committed, segment->segment_info_size); + + // set protection + mi_segment_protect(segment, true, tld->os); + + // insert in free lists for small and medium pages + if (page_kind <= MI_PAGE_MEDIUM) { + mi_segment_insert_in_free_queue(segment, tld); + } + + //fprintf(stderr,"mimalloc: alloc segment at %p\n", (void*)segment); + return segment; +} + +static mi_segment_t* mi_segment_alloc(size_t required, mi_page_kind_t page_kind, size_t page_shift, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) { + return mi_segment_init(NULL, required, page_kind, page_shift, tld, os_tld); +} + +static void mi_segment_free(mi_segment_t* segment, bool force, mi_segments_tld_t* tld) { + UNUSED(force); + mi_assert(segment != NULL); + // note: don't reset pages even on abandon as the whole segment is freed? (and ready for reuse) + bool force_reset = (force && mi_option_is_enabled(mi_option_abandoned_page_reset)); + mi_pages_reset_remove_all_in_segment(segment, force_reset, tld); + mi_segment_remove_from_free_queue(segment,tld); + + mi_assert_expensive(!mi_segment_queue_contains(&tld->small_free, segment)); + mi_assert_expensive(!mi_segment_queue_contains(&tld->medium_free, segment)); + mi_assert(segment->next == NULL); + mi_assert(segment->prev == NULL); + _mi_stat_decrease(&tld->stats->page_committed, segment->segment_info_size); + + if (!force && mi_segment_cache_push(segment, tld)) { + // it is put in our cache + } + else { + // otherwise return it to the OS + mi_segment_os_free(segment, segment->segment_size, tld); + } +} + +/* ----------------------------------------------------------- + Free page management inside a segment +----------------------------------------------------------- */ + + +static bool mi_segment_has_free(const mi_segment_t* segment) { + return (segment->used < segment->capacity); +} + +static bool mi_segment_page_claim(mi_segment_t* segment, mi_page_t* page, mi_segments_tld_t* tld) { + mi_assert_internal(_mi_page_segment(page) == segment); + mi_assert_internal(!page->segment_in_use); + mi_pages_reset_remove(page, tld); + // check commit + if (!page->is_committed) { + mi_assert_internal(!segment->mem_is_fixed); + mi_assert_internal(!page->is_reset); + size_t psize; + uint8_t* start = mi_segment_raw_page_start(segment, page, &psize); + bool is_zero = false; + const size_t gsize = (MI_SECURE >= 2 ? _mi_os_page_size() : 0); + bool ok = _mi_mem_commit(start, psize + gsize, &is_zero, tld->os); + if (!ok) return false; // failed to commit! + if (gsize > 0) { mi_segment_protect_range(start + psize, gsize, true); } + if (is_zero) { page->is_zero_init = true; } + page->is_committed = true; + } + // set in-use before doing unreset to prevent delayed reset + page->segment_in_use = true; + segment->used++; + // check reset + if (page->is_reset) { + mi_assert_internal(!segment->mem_is_fixed); + bool ok = mi_page_unreset(segment, page, 0, tld); + if (!ok) { + page->segment_in_use = false; + segment->used--; + return false; + } + } + mi_assert_internal(page->segment_in_use); + mi_assert_internal(segment->used <= segment->capacity); + if (segment->used == segment->capacity && segment->page_kind <= MI_PAGE_MEDIUM) { + // if no more free pages, remove from the queue + mi_assert_internal(!mi_segment_has_free(segment)); + mi_segment_remove_from_free_queue(segment, tld); + } + return true; +} + + +/* ----------------------------------------------------------- + Free +----------------------------------------------------------- */ + +static void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld); + +// clear page data; can be called on abandoned segments +static void mi_segment_page_clear(mi_segment_t* segment, mi_page_t* page, bool allow_reset, mi_segments_tld_t* tld) +{ + mi_assert_internal(page->segment_in_use); + mi_assert_internal(mi_page_all_free(page)); + mi_assert_internal(page->is_committed); + mi_assert_internal(mi_page_not_in_queue(page, tld)); + + size_t inuse = page->capacity * mi_page_block_size(page); + _mi_stat_decrease(&tld->stats->page_committed, inuse); + _mi_stat_decrease(&tld->stats->pages, 1); + + // calculate the used size from the raw (non-aligned) start of the page + //size_t pre_size; + //_mi_segment_page_start(segment, page, page->block_size, NULL, &pre_size); + //size_t used_size = pre_size + (page->capacity * page->block_size); + + page->is_zero_init = false; + page->segment_in_use = false; + + // reset the page memory to reduce memory pressure? + // note: must come after setting `segment_in_use` to false but before block_size becomes 0 + //mi_page_reset(segment, page, 0 /*used_size*/, tld); + + // zero the page data, but not the segment fields and capacity, and block_size (for page size calculations) + uint32_t block_size = page->xblock_size; + uint16_t capacity = page->capacity; + uint16_t reserved = page->reserved; + ptrdiff_t ofs = offsetof(mi_page_t,capacity); + memset((uint8_t*)page + ofs, 0, sizeof(*page) - ofs); + page->capacity = capacity; + page->reserved = reserved; + page->xblock_size = block_size; + segment->used--; + + // add to the free page list for reuse/reset + if (allow_reset) { + mi_pages_reset_add(segment, page, tld); + } + + page->capacity = 0; // after reset there can be zero'd now + page->reserved = 0; +} + +void _mi_segment_page_free(mi_page_t* page, bool force, mi_segments_tld_t* tld) +{ + mi_assert(page != NULL); + mi_segment_t* segment = _mi_page_segment(page); + mi_assert_expensive(mi_segment_is_valid(segment,tld)); + mi_reset_delayed(tld); + + // mark it as free now + mi_segment_page_clear(segment, page, true, tld); + + if (segment->used == 0) { + // no more used pages; remove from the free list and free the segment + mi_segment_free(segment, force, tld); + } + else { + if (segment->used == segment->abandoned) { + // only abandoned pages; remove from free list and abandon + mi_segment_abandon(segment,tld); + } + else if (segment->used + 1 == segment->capacity) { + mi_assert_internal(segment->page_kind <= MI_PAGE_MEDIUM); // for now we only support small and medium pages + // move back to segments free list + mi_segment_insert_in_free_queue(segment,tld); + } + } +} + + +/* ----------------------------------------------------------- +Abandonment + +When threads terminate, they can leave segments with +live blocks (reached through other threads). Such segments +are "abandoned" and will be reclaimed by other threads to +reuse their pages and/or free them eventually + +We maintain a global list of abandoned segments that are +reclaimed on demand. Since this is shared among threads +the implementation needs to avoid the A-B-A problem on +popping abandoned segments: +We use tagged pointers to avoid accidentially identifying +reused segments, much like stamped references in Java. +Secondly, we maintain a reader counter to avoid resetting +or decommitting segments that have a pending read operation. + +Note: the current implementation is one possible design; +another way might be to keep track of abandoned segments +in the regions. This would have the advantage of keeping +all concurrent code in one place and not needing to deal +with ABA issues. The drawback is that it is unclear how to +scan abandoned segments efficiently in that case as they +would be spread among all other segments in the regions. +----------------------------------------------------------- */ + +// Use the bottom 20-bits (on 64-bit) of the aligned segment pointers +// to put in a tag that increments on update to avoid the A-B-A problem. +#define MI_TAGGED_MASK MI_SEGMENT_MASK +typedef uintptr_t mi_tagged_segment_t; + +static mi_segment_t* mi_tagged_segment_ptr(mi_tagged_segment_t ts) { + return (mi_segment_t*)(ts & ~MI_TAGGED_MASK); +} + +static mi_tagged_segment_t mi_tagged_segment(mi_segment_t* segment, mi_tagged_segment_t ts) { + mi_assert_internal(((uintptr_t)segment & MI_TAGGED_MASK) == 0); + uintptr_t tag = ((ts & MI_TAGGED_MASK) + 1) & MI_TAGGED_MASK; + return ((uintptr_t)segment | tag); +} + +// This is a list of visited abandoned pages that were full at the time. +// this list migrates to `abandoned` when that becomes NULL. The use of +// this list reduces contention and the rate at which segments are visited. +static mi_decl_cache_align volatile _Atomic(mi_segment_t*) abandoned_visited; // = NULL + +// The abandoned page list (tagged as it supports pop) +static mi_decl_cache_align volatile _Atomic(mi_tagged_segment_t) abandoned; // = NULL + +// We also maintain a count of current readers of the abandoned list +// in order to prevent resetting/decommitting segment memory if it might +// still be read. +static mi_decl_cache_align volatile _Atomic(uintptr_t) abandoned_readers; // = 0 + +// Push on the visited list +static void mi_abandoned_visited_push(mi_segment_t* segment) { + mi_assert_internal(segment->thread_id == 0); + mi_assert_internal(segment->abandoned_next == NULL); + mi_assert_internal(segment->next == NULL && segment->prev == NULL); + mi_assert_internal(segment->used > 0); + mi_segment_t* anext; + do { + anext = mi_atomic_read_ptr_relaxed(mi_segment_t, &abandoned_visited); + segment->abandoned_next = anext; + } while (!mi_atomic_cas_ptr_weak(mi_segment_t, &abandoned_visited, segment, anext)); +} + +// Move the visited list to the abandoned list. +static bool mi_abandoned_visited_revisit(void) +{ + // quick check if the visited list is empty + if (mi_atomic_read_ptr_relaxed(mi_segment_t,&abandoned_visited)==NULL) return false; + + // grab the whole visited list + mi_segment_t* first = mi_atomic_exchange_ptr(mi_segment_t, &abandoned_visited, NULL); + if (first == NULL) return false; + + // first try to swap directly if the abandoned list happens to be NULL + const mi_tagged_segment_t ts = mi_atomic_read_relaxed(&abandoned); + mi_tagged_segment_t afirst; + if (mi_tagged_segment_ptr(ts)==NULL) { + afirst = mi_tagged_segment(first, ts); + if (mi_atomic_cas_strong(&abandoned, afirst, ts)) return true; + } + + // find the last element of the visited list: O(n) + mi_segment_t* last = first; + while (last->abandoned_next != NULL) { + last = last->abandoned_next; + } + + // and atomically prepend to the abandoned list + // (no need to increase the readers as we don't access the abandoned segments) + mi_tagged_segment_t anext; + do { + anext = mi_atomic_read_relaxed(&abandoned); + last->abandoned_next = mi_tagged_segment_ptr(anext); + afirst = mi_tagged_segment(first, anext); + } while (!mi_atomic_cas_weak(&abandoned, afirst, anext)); + return true; +} + +// Push on the abandoned list. +static void mi_abandoned_push(mi_segment_t* segment) { + mi_assert_internal(segment->thread_id == 0); + mi_assert_internal(segment->abandoned_next == NULL); + mi_assert_internal(segment->next == NULL && segment->prev == NULL); + mi_assert_internal(segment->used > 0); + mi_tagged_segment_t ts; + mi_tagged_segment_t next; + do { + ts = mi_atomic_read_relaxed(&abandoned); + segment->abandoned_next = mi_tagged_segment_ptr(ts); + next = mi_tagged_segment(segment, ts); + } while (!mi_atomic_cas_weak(&abandoned, next, ts)); +} + +// Wait until there are no more pending reads on segments that used to be in the abandoned list +void _mi_abandoned_await_readers(void) { + uintptr_t n; + do { + n = mi_atomic_read(&abandoned_readers); + if (n != 0) mi_atomic_yield(); + } while (n != 0); +} + +// Pop from the abandoned list +static mi_segment_t* mi_abandoned_pop(void) { + mi_segment_t* segment; + // Check efficiently if it is empty (or if the visited list needs to be moved) + mi_tagged_segment_t ts = mi_atomic_read_relaxed(&abandoned); + segment = mi_tagged_segment_ptr(ts); + if (mi_likely(segment == NULL)) { + if (mi_likely(!mi_abandoned_visited_revisit())) { // try to swap in the visited list on NULL + return NULL; + } + } + + // Do a pop. We use a reader count to prevent + // a segment to be decommitted while a read is still pending, + // and a tagged pointer to prevent A-B-A link corruption. + // (this is called from `memory.c:_mi_mem_free` for example) + mi_atomic_increment(&abandoned_readers); // ensure no segment gets decommitted + mi_tagged_segment_t next = 0; + do { + ts = mi_atomic_read(&abandoned); + segment = mi_tagged_segment_ptr(ts); + if (segment != NULL) { + next = mi_tagged_segment(segment->abandoned_next, ts); // note: reads the segment's `abandoned_next` field so should not be decommitted + } + } while (segment != NULL && !mi_atomic_cas_weak(&abandoned, next, ts)); + mi_atomic_decrement(&abandoned_readers); // release reader lock + if (segment != NULL) { + segment->abandoned_next = NULL; + } + return segment; +} + +/* ----------------------------------------------------------- + Abandon segment/page +----------------------------------------------------------- */ + +static void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld) { + mi_assert_internal(segment->used == segment->abandoned); + mi_assert_internal(segment->used > 0); + mi_assert_internal(segment->abandoned_next == NULL); + mi_assert_expensive(mi_segment_is_valid(segment, tld)); + + // remove the segment from the free page queue if needed + mi_reset_delayed(tld); + mi_pages_reset_remove_all_in_segment(segment, mi_option_is_enabled(mi_option_abandoned_page_reset), tld); + mi_segment_remove_from_free_queue(segment, tld); + mi_assert_internal(segment->next == NULL && segment->prev == NULL); + + // all pages in the segment are abandoned; add it to the abandoned list + _mi_stat_increase(&tld->stats->segments_abandoned, 1); + mi_segments_track_size(-((long)segment->segment_size), tld); + segment->thread_id = 0; + segment->abandoned_next = NULL; + segment->abandoned_visits = 0; + mi_abandoned_push(segment); +} + +void _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld) { + mi_assert(page != NULL); + mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE); + mi_assert_internal(mi_page_heap(page) == NULL); + mi_segment_t* segment = _mi_page_segment(page); + mi_assert_expensive(!mi_pages_reset_contains(page, tld)); + mi_assert_expensive(mi_segment_is_valid(segment, tld)); + segment->abandoned++; + _mi_stat_increase(&tld->stats->pages_abandoned, 1); + mi_assert_internal(segment->abandoned <= segment->used); + if (segment->used == segment->abandoned) { + // all pages are abandoned, abandon the entire segment + mi_segment_abandon(segment, tld); + } +} + +/* ----------------------------------------------------------- + Reclaim abandoned pages +----------------------------------------------------------- */ + +// Possibly clear pages and check if free space is available +static bool mi_segment_check_free(mi_segment_t* segment, size_t block_size, bool* all_pages_free) +{ + mi_assert_internal(block_size < MI_HUGE_BLOCK_SIZE); + bool has_page = false; + size_t pages_used = 0; + size_t pages_used_empty = 0; + for (size_t i = 0; i < segment->capacity; i++) { + mi_page_t* page = &segment->pages[i]; + if (page->segment_in_use) { + pages_used++; + // ensure used count is up to date and collect potential concurrent frees + _mi_page_free_collect(page, false); + if (mi_page_all_free(page)) { + // if everything free already, page can be reused for some block size + // note: don't clear the page yet as we can only OS reset it once it is reclaimed + pages_used_empty++; + has_page = true; + } + else if (page->xblock_size == block_size && mi_page_has_any_available(page)) { + // a page has available free blocks of the right size + has_page = true; + } + } + else { + // whole empty page + has_page = true; + } + } + mi_assert_internal(pages_used == segment->used && pages_used >= pages_used_empty); + if (all_pages_free != NULL) { + *all_pages_free = ((pages_used - pages_used_empty) == 0); + } + return has_page; +} + + +// Reclaim a segment; returns NULL if the segment was freed +// set `right_page_reclaimed` to `true` if it reclaimed a page of the right `block_size` that was not full. +static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap, size_t requested_block_size, bool* right_page_reclaimed, mi_segments_tld_t* tld) { + mi_assert_internal(segment->abandoned_next == NULL); + if (right_page_reclaimed != NULL) { *right_page_reclaimed = false; } + + segment->thread_id = _mi_thread_id(); + segment->abandoned_visits = 0; + mi_segments_track_size((long)segment->segment_size, tld); + mi_assert_internal(segment->next == NULL && segment->prev == NULL); + mi_assert_expensive(mi_segment_is_valid(segment, tld)); + _mi_stat_decrease(&tld->stats->segments_abandoned, 1); + + for (size_t i = 0; i < segment->capacity; i++) { + mi_page_t* page = &segment->pages[i]; + if (page->segment_in_use) { + mi_assert_internal(!page->is_reset); + mi_assert_internal(page->is_committed); + mi_assert_internal(mi_page_not_in_queue(page, tld)); + mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE); + mi_assert_internal(mi_page_heap(page) == NULL); + segment->abandoned--; + mi_assert(page->next == NULL); + _mi_stat_decrease(&tld->stats->pages_abandoned, 1); + // set the heap again and allow heap thread delayed free again. + mi_page_set_heap(page, heap); + _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, true); // override never (after heap is set) + // TODO: should we not collect again given that we just collected in `check_free`? + _mi_page_free_collect(page, false); // ensure used count is up to date + if (mi_page_all_free(page)) { + // if everything free already, clear the page directly + mi_segment_page_clear(segment, page, true, tld); // reset is ok now + } + else { + // otherwise reclaim it into the heap + _mi_page_reclaim(heap, page); + if (requested_block_size == page->xblock_size && mi_page_has_any_available(page)) { + if (right_page_reclaimed != NULL) { *right_page_reclaimed = true; } + } + } + } + else if (page->is_committed && !page->is_reset) { // not in-use, and not reset yet + // note: do not reset as this includes pages that were not touched before + // mi_pages_reset_add(segment, page, tld); + } + } + mi_assert_internal(segment->abandoned == 0); + if (segment->used == 0) { + mi_assert_internal(right_page_reclaimed == NULL || !(*right_page_reclaimed)); + mi_segment_free(segment, false, tld); + return NULL; + } + else { + if (segment->page_kind <= MI_PAGE_MEDIUM && mi_segment_has_free(segment)) { + mi_segment_insert_in_free_queue(segment, tld); + } + return segment; + } +} + + +void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld) { + mi_segment_t* segment; + while ((segment = mi_abandoned_pop()) != NULL) { + mi_segment_reclaim(segment, heap, 0, NULL, tld); + } +} + +static mi_segment_t* mi_segment_try_reclaim(mi_heap_t* heap, size_t block_size, mi_page_kind_t page_kind, bool* reclaimed, mi_segments_tld_t* tld) +{ + *reclaimed = false; + mi_segment_t* segment; + int max_tries = 8; // limit the work to bound allocation times + while ((max_tries-- > 0) && ((segment = mi_abandoned_pop()) != NULL)) { + segment->abandoned_visits++; + bool all_pages_free; + bool has_page = mi_segment_check_free(segment,block_size,&all_pages_free); // try to free up pages (due to concurrent frees) + if (all_pages_free) { + // free the segment (by forced reclaim) to make it available to other threads. + // note1: we prefer to free a segment as that might lead to reclaiming another + // segment that is still partially used. + // note2: we could in principle optimize this by skipping reclaim and directly + // freeing but that would violate some invariants temporarily) + mi_segment_reclaim(segment, heap, 0, NULL, tld); + } + else if (has_page && segment->page_kind == page_kind) { + // found a free page of the right kind, or page of the right block_size with free space + // we return the result of reclaim (which is usually `segment`) as it might free + // the segment due to concurrent frees (in which case `NULL` is returned). + return mi_segment_reclaim(segment, heap, block_size, reclaimed, tld); + } + else if (segment->abandoned_visits >= 3) { + // always reclaim on 3rd visit to limit the list length. + mi_segment_reclaim(segment, heap, 0, NULL, tld); + } + else { + // otherwise, push on the visited list so it gets not looked at too quickly again + mi_abandoned_visited_push(segment); + } + } + return NULL; +} + + +/* ----------------------------------------------------------- + Reclaim or allocate +----------------------------------------------------------- */ + +static mi_segment_t* mi_segment_reclaim_or_alloc(mi_heap_t* heap, size_t block_size, mi_page_kind_t page_kind, size_t page_shift, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) +{ + mi_assert_internal(page_kind <= MI_PAGE_LARGE); + mi_assert_internal(block_size < MI_HUGE_BLOCK_SIZE); + // 1. try to get a segment from our cache + mi_segment_t* segment = mi_segment_cache_pop(MI_SEGMENT_SIZE, tld); + if (segment != NULL) { + mi_segment_init(segment, 0, page_kind, page_shift, tld, os_tld); + return segment; + } + // 2. try to reclaim an abandoned segment + bool reclaimed; + segment = mi_segment_try_reclaim(heap, block_size, page_kind, &reclaimed, tld); + if (reclaimed) { + // reclaimed the right page right into the heap + mi_assert_internal(segment != NULL && segment->page_kind == page_kind && page_kind <= MI_PAGE_LARGE); + return NULL; // pretend out-of-memory as the page will be in the page queue of the heap with available blocks + } + else if (segment != NULL) { + // reclaimed a segment with empty pages (of `page_kind`) in it + return segment; + } + // 3. otherwise allocate a fresh segment + return mi_segment_alloc(0, page_kind, page_shift, tld, os_tld); +} + + +/* ----------------------------------------------------------- + Small page allocation +----------------------------------------------------------- */ + +static mi_page_t* mi_segment_find_free(mi_segment_t* segment, mi_segments_tld_t* tld) { + mi_assert_internal(mi_segment_has_free(segment)); + mi_assert_expensive(mi_segment_is_valid(segment, tld)); + for (size_t i = 0; i < segment->capacity; i++) { // TODO: use a bitmap instead of search? + mi_page_t* page = &segment->pages[i]; + if (!page->segment_in_use) { + bool ok = mi_segment_page_claim(segment, page, tld); + if (ok) return page; + } + } + mi_assert(false); + return NULL; +} + +// Allocate a page inside a segment. Requires that the page has free pages +static mi_page_t* mi_segment_page_alloc_in(mi_segment_t* segment, mi_segments_tld_t* tld) { + mi_assert_internal(mi_segment_has_free(segment)); + return mi_segment_find_free(segment, tld); +} + +static mi_page_t* mi_segment_page_alloc(mi_heap_t* heap, size_t block_size, mi_page_kind_t kind, size_t page_shift, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) { + // find an available segment the segment free queue + mi_segment_queue_t* const free_queue = mi_segment_free_queue_of_kind(kind, tld); + if (mi_segment_queue_is_empty(free_queue)) { + // possibly allocate or reclaim a fresh segment + mi_segment_t* const segment = mi_segment_reclaim_or_alloc(heap, block_size, kind, page_shift, tld, os_tld); + if (segment == NULL) return NULL; // return NULL if out-of-memory (or reclaimed) + mi_assert_internal(free_queue->first == segment); + mi_assert_internal(segment->page_kind==kind); + mi_assert_internal(segment->used < segment->capacity); + } + mi_assert_internal(free_queue->first != NULL); + mi_page_t* const page = mi_segment_page_alloc_in(free_queue->first, tld); + mi_assert_internal(page != NULL); +#if MI_DEBUG>=2 + // verify it is committed + _mi_segment_page_start(_mi_page_segment(page), page, sizeof(void*), NULL, NULL)[0] = 0; +#endif + return page; +} + +static mi_page_t* mi_segment_small_page_alloc(mi_heap_t* heap, size_t block_size, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) { + return mi_segment_page_alloc(heap, block_size, MI_PAGE_SMALL,MI_SMALL_PAGE_SHIFT,tld,os_tld); +} + +static mi_page_t* mi_segment_medium_page_alloc(mi_heap_t* heap, size_t block_size, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) { + return mi_segment_page_alloc(heap, block_size, MI_PAGE_MEDIUM, MI_MEDIUM_PAGE_SHIFT, tld, os_tld); +} + +/* ----------------------------------------------------------- + large page allocation +----------------------------------------------------------- */ + +static mi_page_t* mi_segment_large_page_alloc(mi_heap_t* heap, size_t block_size, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) { + mi_segment_t* segment = mi_segment_reclaim_or_alloc(heap,block_size,MI_PAGE_LARGE,MI_LARGE_PAGE_SHIFT,tld,os_tld); + if (segment == NULL) return NULL; + mi_page_t* page = mi_segment_find_free(segment, tld); + mi_assert_internal(page != NULL); +#if MI_DEBUG>=2 + _mi_segment_page_start(segment, page, sizeof(void*), NULL, NULL)[0] = 0; +#endif + return page; +} + +static mi_page_t* mi_segment_huge_page_alloc(size_t size, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) +{ + mi_segment_t* segment = mi_segment_alloc(size, MI_PAGE_HUGE, MI_SEGMENT_SHIFT,tld,os_tld); + if (segment == NULL) return NULL; + mi_assert_internal(mi_segment_page_size(segment) - segment->segment_info_size - (2*(MI_SECURE == 0 ? 0 : _mi_os_page_size())) >= size); + segment->thread_id = 0; // huge pages are immediately abandoned + mi_segments_track_size(-(long)segment->segment_size, tld); + mi_page_t* page = mi_segment_find_free(segment, tld); + mi_assert_internal(page != NULL); + return page; +} + +// free huge block from another thread +void _mi_segment_huge_page_free(mi_segment_t* segment, mi_page_t* page, mi_block_t* block) { + // huge page segments are always abandoned and can be freed immediately by any thread + mi_assert_internal(segment->page_kind==MI_PAGE_HUGE); + mi_assert_internal(segment == _mi_page_segment(page)); + mi_assert_internal(mi_atomic_read_relaxed(&segment->thread_id)==0); + + // claim it and free + mi_heap_t* heap = mi_heap_get_default(); // issue #221; don't use the internal get_default_heap as we need to ensure the thread is initialized. + // paranoia: if this it the last reference, the cas should always succeed + if (mi_atomic_cas_strong(&segment->thread_id, heap->thread_id, 0)) { + mi_block_set_next(page, block, page->free); + page->free = block; + page->used--; + page->is_zero = false; + mi_assert(page->used == 0); + mi_tld_t* tld = heap->tld; + const size_t bsize = mi_page_usable_block_size(page); + if (bsize > MI_HUGE_OBJ_SIZE_MAX) { + _mi_stat_decrease(&tld->stats.giant, bsize); + } + else { + _mi_stat_decrease(&tld->stats.huge, bsize); + } + mi_segments_track_size((long)segment->segment_size, &tld->segments); + _mi_segment_page_free(page, true, &tld->segments); + } +} + +/* ----------------------------------------------------------- + Page allocation +----------------------------------------------------------- */ + +mi_page_t* _mi_segment_page_alloc(mi_heap_t* heap, size_t block_size, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) { + mi_page_t* page; + if (block_size <= MI_SMALL_OBJ_SIZE_MAX) { + page = mi_segment_small_page_alloc(heap, block_size, tld, os_tld); + } + else if (block_size <= MI_MEDIUM_OBJ_SIZE_MAX) { + page = mi_segment_medium_page_alloc(heap, block_size, tld, os_tld); + } + else if (block_size <= MI_LARGE_OBJ_SIZE_MAX) { + page = mi_segment_large_page_alloc(heap, block_size, tld, os_tld); + } + else { + page = mi_segment_huge_page_alloc(block_size,tld,os_tld); + } + mi_assert_expensive(page == NULL || mi_segment_is_valid(_mi_page_segment(page),tld)); + mi_assert_internal(page == NULL || (mi_segment_page_size(_mi_page_segment(page)) - (MI_SECURE == 0 ? 0 : _mi_os_page_size())) >= block_size); + mi_reset_delayed(tld); + mi_assert_internal(page == NULL || mi_page_not_in_queue(page, tld)); + return page; +} diff --git a/extlib/mimalloc/src/static.c b/extlib/mimalloc/src/static.c new file mode 100644 index 0000000..d024b01 --- /dev/null +++ b/extlib/mimalloc/src/static.c @@ -0,0 +1,38 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif +#if defined(__sun) +// same remarks as os.c for the static's context. +#undef _XOPEN_SOURCE +#undef _POSIX_C_SOURCE +#endif + +#include "mimalloc.h" +#include "mimalloc-internal.h" + +// For a static override we create a single object file +// containing the whole library. If it is linked first +// it will override all the standard library allocation +// functions (on Unix's). +#include "stats.c" +#include "random.c" +#include "os.c" +#include "arena.c" +#include "region.c" +#include "segment.c" +#include "page.c" +#include "heap.c" +#include "alloc.c" +#include "alloc-aligned.c" +#include "alloc-posix.c" +#if MI_OSX_ZONE +#include "alloc-override-osx.c" +#endif +#include "init.c" +#include "options.c" diff --git a/extlib/mimalloc/src/stats.c b/extlib/mimalloc/src/stats.c new file mode 100644 index 0000000..203bad8 --- /dev/null +++ b/extlib/mimalloc/src/stats.c @@ -0,0 +1,532 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc-internal.h" +#include "mimalloc-atomic.h" + +#include // fputs, stderr +#include // memset + + +/* ----------------------------------------------------------- + Statistics operations +----------------------------------------------------------- */ + +static bool mi_is_in_main(void* stat) { + return ((uint8_t*)stat >= (uint8_t*)&_mi_stats_main + && (uint8_t*)stat < ((uint8_t*)&_mi_stats_main + sizeof(mi_stats_t))); +} + +static void mi_stat_update(mi_stat_count_t* stat, int64_t amount) { + if (amount == 0) return; + if (mi_is_in_main(stat)) + { + // add atomically (for abandoned pages) + mi_atomic_addi64(&stat->current,amount); + mi_atomic_maxi64(&stat->peak, mi_atomic_readi64(&stat->current)); + if (amount > 0) { + mi_atomic_addi64(&stat->allocated,amount); + } + else { + mi_atomic_addi64(&stat->freed, -amount); + } + } + else { + // add thread local + stat->current += amount; + if (stat->current > stat->peak) stat->peak = stat->current; + if (amount > 0) { + stat->allocated += amount; + } + else { + stat->freed += -amount; + } + } +} + +void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount) { + if (mi_is_in_main(stat)) { + mi_atomic_addi64( &stat->count, 1 ); + mi_atomic_addi64( &stat->total, (int64_t)amount ); + } + else { + stat->count++; + stat->total += amount; + } +} + +void _mi_stat_increase(mi_stat_count_t* stat, size_t amount) { + mi_stat_update(stat, (int64_t)amount); +} + +void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount) { + mi_stat_update(stat, -((int64_t)amount)); +} + +// must be thread safe as it is called from stats_merge +static void mi_stat_add(mi_stat_count_t* stat, const mi_stat_count_t* src, int64_t unit) { + if (stat==src) return; + if (src->allocated==0 && src->freed==0) return; + mi_atomic_addi64( &stat->allocated, src->allocated * unit); + mi_atomic_addi64( &stat->current, src->current * unit); + mi_atomic_addi64( &stat->freed, src->freed * unit); + // peak scores do not work across threads.. + mi_atomic_addi64( &stat->peak, src->peak * unit); +} + +static void mi_stat_counter_add(mi_stat_counter_t* stat, const mi_stat_counter_t* src, int64_t unit) { + if (stat==src) return; + mi_atomic_addi64( &stat->total, src->total * unit); + mi_atomic_addi64( &stat->count, src->count * unit); +} + +// must be thread safe as it is called from stats_merge +static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) { + if (stats==src) return; + mi_stat_add(&stats->segments, &src->segments,1); + mi_stat_add(&stats->pages, &src->pages,1); + mi_stat_add(&stats->reserved, &src->reserved, 1); + mi_stat_add(&stats->committed, &src->committed, 1); + mi_stat_add(&stats->reset, &src->reset, 1); + mi_stat_add(&stats->page_committed, &src->page_committed, 1); + + mi_stat_add(&stats->pages_abandoned, &src->pages_abandoned, 1); + mi_stat_add(&stats->segments_abandoned, &src->segments_abandoned, 1); + mi_stat_add(&stats->threads, &src->threads, 1); + + mi_stat_add(&stats->malloc, &src->malloc, 1); + mi_stat_add(&stats->segments_cache, &src->segments_cache, 1); + mi_stat_add(&stats->huge, &src->huge, 1); + mi_stat_add(&stats->giant, &src->giant, 1); + + mi_stat_counter_add(&stats->pages_extended, &src->pages_extended, 1); + mi_stat_counter_add(&stats->mmap_calls, &src->mmap_calls, 1); + mi_stat_counter_add(&stats->commit_calls, &src->commit_calls, 1); + + mi_stat_counter_add(&stats->page_no_retire, &src->page_no_retire, 1); + mi_stat_counter_add(&stats->searches, &src->searches, 1); + mi_stat_counter_add(&stats->huge_count, &src->huge_count, 1); + mi_stat_counter_add(&stats->giant_count, &src->giant_count, 1); +#if MI_STAT>1 + for (size_t i = 0; i <= MI_BIN_HUGE; i++) { + if (src->normal[i].allocated > 0 || src->normal[i].freed > 0) { + mi_stat_add(&stats->normal[i], &src->normal[i], 1); + } + } +#endif +} + +/* ----------------------------------------------------------- + Display statistics +----------------------------------------------------------- */ + +// unit > 0 : size in binary bytes +// unit == 0: count as decimal +// unit < 0 : count in binary +static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg, const char* fmt) { + char buf[32]; + int len = 32; + const char* suffix = (unit <= 0 ? " " : "b"); + const int64_t base = (unit == 0 ? 1000 : 1024); + if (unit>0) n *= unit; + + const int64_t pos = (n < 0 ? -n : n); + if (pos < base) { + snprintf(buf, len, "%d %s ", (int)n, suffix); + } + else { + int64_t divider = base; + const char* magnitude = "k"; + if (pos >= divider*base) { divider *= base; magnitude = "m"; } + if (pos >= divider*base) { divider *= base; magnitude = "g"; } + const int64_t tens = (n / (divider/10)); + const long whole = (long)(tens/10); + const long frac1 = (long)(tens%10); + snprintf(buf, len, "%ld.%ld %s%s", whole, frac1, magnitude, suffix); + } + _mi_fprintf(out, arg, (fmt==NULL ? "%11s" : fmt), buf); +} + + +static void mi_print_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg) { + mi_printf_amount(n,unit,out,arg,NULL); +} + +static void mi_print_count(int64_t n, int64_t unit, mi_output_fun* out, void* arg) { + if (unit==1) _mi_fprintf(out, arg, "%11s"," "); + else mi_print_amount(n,0,out,arg); +} + +static void mi_stat_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg ) { + _mi_fprintf(out, arg,"%10s:", msg); + if (unit>0) { + mi_print_amount(stat->peak, unit, out, arg); + mi_print_amount(stat->allocated, unit, out, arg); + mi_print_amount(stat->freed, unit, out, arg); + mi_print_amount(unit, 1, out, arg); + mi_print_count(stat->allocated, unit, out, arg); + if (stat->allocated > stat->freed) + _mi_fprintf(out, arg, " not all freed!\n"); + else + _mi_fprintf(out, arg, " ok\n"); + } + else if (unit<0) { + mi_print_amount(stat->peak, -1, out, arg); + mi_print_amount(stat->allocated, -1, out, arg); + mi_print_amount(stat->freed, -1, out, arg); + if (unit==-1) { + _mi_fprintf(out, arg, "%22s", ""); + } + else { + mi_print_amount(-unit, 1, out, arg); + mi_print_count((stat->allocated / -unit), 0, out, arg); + } + if (stat->allocated > stat->freed) + _mi_fprintf(out, arg, " not all freed!\n"); + else + _mi_fprintf(out, arg, " ok\n"); + } + else { + mi_print_amount(stat->peak, 1, out, arg); + mi_print_amount(stat->allocated, 1, out, arg); + _mi_fprintf(out, arg, "\n"); + } +} + +static void mi_stat_counter_print(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) { + _mi_fprintf(out, arg, "%10s:", msg); + mi_print_amount(stat->total, -1, out, arg); + _mi_fprintf(out, arg, "\n"); +} + +static void mi_stat_counter_print_avg(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg) { + const int64_t avg_tens = (stat->count == 0 ? 0 : (stat->total*10 / stat->count)); + const long avg_whole = (long)(avg_tens/10); + const long avg_frac1 = (long)(avg_tens%10); + _mi_fprintf(out, arg, "%10s: %5ld.%ld avg\n", msg, avg_whole, avg_frac1); +} + + +static void mi_print_header(mi_output_fun* out, void* arg ) { + _mi_fprintf(out, arg, "%10s: %10s %10s %10s %10s %10s\n", "heap stats", "peak ", "total ", "freed ", "unit ", "count "); +} + +#if MI_STAT>1 +static void mi_stats_print_bins(mi_stat_count_t* all, const mi_stat_count_t* bins, size_t max, const char* fmt, mi_output_fun* out, void* arg) { + bool found = false; + char buf[64]; + for (size_t i = 0; i <= max; i++) { + if (bins[i].allocated > 0) { + found = true; + int64_t unit = _mi_bin_size((uint8_t)i); + snprintf(buf, 64, "%s %3zu", fmt, i); + mi_stat_add(all, &bins[i], unit); + mi_stat_print(&bins[i], buf, unit, out, arg); + } + } + //snprintf(buf, 64, "%s all", fmt); + //mi_stat_print(all, buf, 1); + if (found) { + _mi_fprintf(out, arg, "\n"); + mi_print_header(out, arg); + } +} +#endif + + + +//------------------------------------------------------------ +// Use an output wrapper for line-buffered output +// (which is nice when using loggers etc.) +//------------------------------------------------------------ +typedef struct buffered_s { + mi_output_fun* out; // original output function + void* arg; // and state + char* buf; // local buffer of at least size `count+1` + size_t used; // currently used chars `used <= count` + size_t count; // total chars available for output +} buffered_t; + +static void mi_buffered_flush(buffered_t* buf) { + buf->buf[buf->used] = 0; + _mi_fputs(buf->out, buf->arg, NULL, buf->buf); + buf->used = 0; +} + +static void mi_buffered_out(const char* msg, void* arg) { + buffered_t* buf = (buffered_t*)arg; + if (msg==NULL || buf==NULL) return; + for (const char* src = msg; *src != 0; src++) { + char c = *src; + if (buf->used >= buf->count) mi_buffered_flush(buf); + mi_assert_internal(buf->used < buf->count); + buf->buf[buf->used++] = c; + if (c == '\n') mi_buffered_flush(buf); + } +} + +//------------------------------------------------------------ +// Print statistics +//------------------------------------------------------------ + +static void mi_process_info(mi_msecs_t* utime, mi_msecs_t* stime, size_t* peak_rss, size_t* page_faults, size_t* page_reclaim, size_t* peak_commit); + +static void _mi_stats_print(mi_stats_t* stats, mi_msecs_t elapsed, mi_output_fun* out0, void* arg0) mi_attr_noexcept { + // wrap the output function to be line buffered + char buf[256]; + buffered_t buffer = { out0, arg0, buf, 0, 255 }; + mi_output_fun* out = &mi_buffered_out; + void* arg = &buffer; + + // and print using that + mi_print_header(out,arg); + #if MI_STAT>1 + mi_stat_count_t normal = { 0,0,0,0 }; + mi_stats_print_bins(&normal, stats->normal, MI_BIN_HUGE, "normal",out,arg); + mi_stat_print(&normal, "normal", 1, out, arg); + mi_stat_print(&stats->huge, "huge", (stats->huge_count.count == 0 ? 1 : -(stats->huge.allocated / stats->huge_count.count)), out, arg); + mi_stat_print(&stats->giant, "giant", (stats->giant_count.count == 0 ? 1 : -(stats->giant.allocated / stats->giant_count.count)), out, arg); + mi_stat_count_t total = { 0,0,0,0 }; + mi_stat_add(&total, &normal, 1); + mi_stat_add(&total, &stats->huge, 1); + mi_stat_add(&total, &stats->giant, 1); + mi_stat_print(&total, "total", 1, out, arg); + _mi_fprintf(out, arg, "malloc requested: "); + mi_print_amount(stats->malloc.allocated, 1, out, arg); + _mi_fprintf(out, arg, "\n\n"); + #endif + mi_stat_print(&stats->reserved, "reserved", 1, out, arg); + mi_stat_print(&stats->committed, "committed", 1, out, arg); + mi_stat_print(&stats->reset, "reset", 1, out, arg); + mi_stat_print(&stats->page_committed, "touched", 1, out, arg); + mi_stat_print(&stats->segments, "segments", -1, out, arg); + mi_stat_print(&stats->segments_abandoned, "-abandoned", -1, out, arg); + mi_stat_print(&stats->segments_cache, "-cached", -1, out, arg); + mi_stat_print(&stats->pages, "pages", -1, out, arg); + mi_stat_print(&stats->pages_abandoned, "-abandoned", -1, out, arg); + mi_stat_counter_print(&stats->pages_extended, "-extended", out, arg); + mi_stat_counter_print(&stats->page_no_retire, "-noretire", out, arg); + mi_stat_counter_print(&stats->mmap_calls, "mmaps", out, arg); + mi_stat_counter_print(&stats->commit_calls, "commits", out, arg); + mi_stat_print(&stats->threads, "threads", -1, out, arg); + mi_stat_counter_print_avg(&stats->searches, "searches", out, arg); + _mi_fprintf(out, arg, "%10s: %7i\n", "numa nodes", _mi_os_numa_node_count()); + if (elapsed > 0) _mi_fprintf(out, arg, "%10s: %7ld.%03ld s\n", "elapsed", elapsed/1000, elapsed%1000); + + mi_msecs_t user_time; + mi_msecs_t sys_time; + size_t peak_rss; + size_t page_faults; + size_t page_reclaim; + size_t peak_commit; + mi_process_info(&user_time, &sys_time, &peak_rss, &page_faults, &page_reclaim, &peak_commit); + _mi_fprintf(out, arg, "%10s: user: %ld.%03ld s, system: %ld.%03ld s, faults: %lu, reclaims: %lu, rss: ", "process", user_time/1000, user_time%1000, sys_time/1000, sys_time%1000, (unsigned long)page_faults, (unsigned long)page_reclaim ); + mi_printf_amount((int64_t)peak_rss, 1, out, arg, "%s"); + if (peak_commit > 0) { + _mi_fprintf(out, arg, ", commit charge: "); + mi_printf_amount((int64_t)peak_commit, 1, out, arg, "%s"); + } + _mi_fprintf(out, arg, "\n"); +} + +static mi_msecs_t mi_time_start; // = 0 + +static mi_stats_t* mi_stats_get_default(void) { + mi_heap_t* heap = mi_heap_get_default(); + return &heap->tld->stats; +} + +static void mi_stats_merge_from(mi_stats_t* stats) { + if (stats != &_mi_stats_main) { + mi_stats_add(&_mi_stats_main, stats); + memset(stats, 0, sizeof(mi_stats_t)); + } +} + +void mi_stats_reset(void) mi_attr_noexcept { + mi_stats_t* stats = mi_stats_get_default(); + if (stats != &_mi_stats_main) { memset(stats, 0, sizeof(mi_stats_t)); } + memset(&_mi_stats_main, 0, sizeof(mi_stats_t)); + mi_time_start = _mi_clock_start(); +} + +void mi_stats_merge(void) mi_attr_noexcept { + mi_stats_merge_from( mi_stats_get_default() ); +} + +void _mi_stats_done(mi_stats_t* stats) { // called from `mi_thread_done` + mi_stats_merge_from(stats); +} + +void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept { + mi_msecs_t elapsed = _mi_clock_end(mi_time_start); + mi_stats_merge_from(mi_stats_get_default()); + _mi_stats_print(&_mi_stats_main, elapsed, out, arg); +} + +void mi_stats_print(void* out) mi_attr_noexcept { + // for compatibility there is an `out` parameter (which can be `stdout` or `stderr`) + mi_stats_print_out((mi_output_fun*)out, NULL); +} + +void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept { + mi_msecs_t elapsed = _mi_clock_end(mi_time_start); + _mi_stats_print(mi_stats_get_default(), elapsed, out, arg); +} + + +// ---------------------------------------------------------------- +// Basic timer for convenience; use milli-seconds to avoid doubles +// ---------------------------------------------------------------- +#ifdef _WIN32 +#include +static mi_msecs_t mi_to_msecs(LARGE_INTEGER t) { + static LARGE_INTEGER mfreq; // = 0 + if (mfreq.QuadPart == 0LL) { + LARGE_INTEGER f; + QueryPerformanceFrequency(&f); + mfreq.QuadPart = f.QuadPart/1000LL; + if (mfreq.QuadPart == 0) mfreq.QuadPart = 1; + } + return (mi_msecs_t)(t.QuadPart / mfreq.QuadPart); +} + +mi_msecs_t _mi_clock_now(void) { + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return mi_to_msecs(t); +} +#else +#include +#ifdef CLOCK_REALTIME +mi_msecs_t _mi_clock_now(void) { + struct timespec t; + clock_gettime(CLOCK_REALTIME, &t); + return ((mi_msecs_t)t.tv_sec * 1000) + ((mi_msecs_t)t.tv_nsec / 1000000); +} +#else +// low resolution timer +mi_msecs_t _mi_clock_now(void) { + return ((mi_msecs_t)clock() / ((mi_msecs_t)CLOCKS_PER_SEC / 1000)); +} +#endif +#endif + + +static mi_msecs_t mi_clock_diff; + +mi_msecs_t _mi_clock_start(void) { + if (mi_clock_diff == 0.0) { + mi_msecs_t t0 = _mi_clock_now(); + mi_clock_diff = _mi_clock_now() - t0; + } + return _mi_clock_now(); +} + +mi_msecs_t _mi_clock_end(mi_msecs_t start) { + mi_msecs_t end = _mi_clock_now(); + return (end - start - mi_clock_diff); +} + + +// -------------------------------------------------------- +// Basic process statistics +// -------------------------------------------------------- + +#if defined(_WIN32) +#include +#include +#pragma comment(lib,"psapi.lib") + +static mi_msecs_t filetime_msecs(const FILETIME* ftime) { + ULARGE_INTEGER i; + i.LowPart = ftime->dwLowDateTime; + i.HighPart = ftime->dwHighDateTime; + mi_msecs_t msecs = (i.QuadPart / 10000); // FILETIME is in 100 nano seconds + return msecs; +} +static void mi_process_info(mi_msecs_t* utime, mi_msecs_t* stime, size_t* peak_rss, size_t* page_faults, size_t* page_reclaim, size_t* peak_commit) { + FILETIME ct; + FILETIME ut; + FILETIME st; + FILETIME et; + GetProcessTimes(GetCurrentProcess(), &ct, &et, &st, &ut); + *utime = filetime_msecs(&ut); + *stime = filetime_msecs(&st); + + PROCESS_MEMORY_COUNTERS info; + GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info)); + *peak_rss = (size_t)info.PeakWorkingSetSize; + *page_faults = (size_t)info.PageFaultCount; + *peak_commit = (size_t)info.PeakPagefileUsage; + *page_reclaim = 0; +} + +#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__)) || defined(__HAIKU__) +#include +#include +#include + +#if defined(__APPLE__) && defined(__MACH__) +#include +#endif + +#if defined(__HAIKU__) +#include +#endif + +static mi_msecs_t timeval_secs(const struct timeval* tv) { + return ((mi_msecs_t)tv->tv_sec * 1000L) + ((mi_msecs_t)tv->tv_usec / 1000L); +} + +static void mi_process_info(mi_msecs_t* utime, mi_msecs_t* stime, size_t* peak_rss, size_t* page_faults, size_t* page_reclaim, size_t* peak_commit) { + struct rusage rusage; + getrusage(RUSAGE_SELF, &rusage); +#if !defined(__HAIKU__) +#if defined(__APPLE__) && defined(__MACH__) + *peak_rss = rusage.ru_maxrss; +#else + *peak_rss = rusage.ru_maxrss * 1024; +#endif + *page_faults = rusage.ru_majflt; + *page_reclaim = rusage.ru_minflt; + *peak_commit = 0; +#else +// Haiku does not have (yet?) a way to +// get these stats per process + thread_info tid; + area_info mem; + ssize_t c; + *peak_rss = 0; + *page_faults = 0; + *page_reclaim = 0; + *peak_commit = 0; + get_thread_info(find_thread(0), &tid); + + while (get_next_area_info(tid.team, &c, &mem) == B_OK) { + *peak_rss += mem.ram_size; + } +#endif + *utime = timeval_secs(&rusage.ru_utime); + *stime = timeval_secs(&rusage.ru_stime); +} + +#else +#ifndef __wasi__ +// WebAssembly instances are not processes +#pragma message("define a way to get process info") +#endif + +static void mi_process_info(mi_msecs_t* utime, mi_msecs_t* stime, size_t* peak_rss, size_t* page_faults, size_t* page_reclaim, size_t* peak_commit) { + *peak_rss = 0; + *page_faults = 0; + *page_reclaim = 0; + *peak_commit = 0; + *utime = 0; + *stime = 0; +} +#endif diff --git a/extlib/mimalloc/test/CMakeLists.txt b/extlib/mimalloc/test/CMakeLists.txt new file mode 100644 index 0000000..4152f99 --- /dev/null +++ b/extlib/mimalloc/test/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.0) +project(mimalloc-test C CXX) + +# Set default build type +if (NOT CMAKE_BUILD_TYPE) + if ("${CMAKE_BINARY_DIR}" MATCHES ".*(D|d)ebug$") + message(STATUS "No build type selected, default to *** Debug ***") + set(CMAKE_BUILD_TYPE "Debug") + else() + message(STATUS "No build type selected, default to *** Release ***") + set(CMAKE_BUILD_TYPE "Release") + endif() +endif() + +# Import mimalloc (if installed) +find_package(mimalloc 1.6 REQUIRED NO_SYSTEM_ENVIRONMENT_PATH) +message(STATUS "Found mimalloc installed at: ${MIMALLOC_TARGET_DIR}") + +# overriding with a dynamic library +add_executable(dynamic-override main-override.c) +target_link_libraries(dynamic-override PUBLIC mimalloc) + +add_executable(dynamic-override-cxx main-override.cpp) +target_link_libraries(dynamic-override-cxx PUBLIC mimalloc) + + +# overriding with a static object file works reliable as the symbols in the +# object file have priority over those in library files +add_executable(static-override-obj main-override.c ${MIMALLOC_TARGET_DIR}/mimalloc.o) +target_include_directories(static-override-obj PUBLIC ${MIMALLOC_TARGET_DIR}/include) +target_link_libraries(static-override-obj PUBLIC pthread) + + +# overriding with a static library works too if using the `mimalloc-override.h` +# header to redefine malloc/free. (the library already overrides new/delete) +add_executable(static-override-static main-override-static.c) +target_link_libraries(static-override-static PUBLIC mimalloc-static) + + +# overriding with a static library: this may not work if the library is linked too late +# on the command line after the C runtime library; but we cannot control that well in CMake +add_executable(static-override main-override.c) +target_link_libraries(static-override PUBLIC mimalloc-static) + +add_executable(static-override-cxx main-override.cpp) +target_link_libraries(static-override-cxx PUBLIC mimalloc-static) diff --git a/extlib/mimalloc/test/main-override-static.c b/extlib/mimalloc/test/main-override-static.c new file mode 100644 index 0000000..f738c51 --- /dev/null +++ b/extlib/mimalloc/test/main-override-static.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include + +#include +#include // redefines malloc etc. + +static void double_free1(); +static void double_free2(); +static void corrupt_free(); +static void block_overflow1(); +static void invalid_free(); + +int main() { + mi_version(); + + // detect double frees and heap corruption + // double_free1(); + // double_free2(); + // corrupt_free(); + // block_overflow1(); + invalid_free(); + + void* p1 = malloc(78); + void* p2 = malloc(24); + free(p1); + p1 = mi_malloc(8); + //char* s = strdup("hello\n"); + free(p2); + p2 = malloc(16); + p1 = realloc(p1, 32); + free(p1); + free(p2); + //free(s); + //mi_collect(true); + + /* now test if override worked by allocating/freeing across the api's*/ + //p1 = mi_malloc(32); + //free(p1); + //p2 = malloc(32); + //mi_free(p2); + mi_stats_print(NULL); + return 0; +} + +static void invalid_free() { + free((void*)0xBADBEEF); + realloc((void*)0xBADBEEF,10); +} + +static void block_overflow1() { + uint8_t* p = (uint8_t*)mi_malloc(17); + p[18] = 0; + free(p); +} + +// The double free samples come ArcHeap [1] by Insu Yun (issue #161) +// [1]: https://arxiv.org/pdf/1903.00503.pdf + +static void double_free1() { + void* p[256]; + //uintptr_t buf[256]; + + p[0] = mi_malloc(622616); + p[1] = mi_malloc(655362); + p[2] = mi_malloc(786432); + mi_free(p[2]); + // [VULN] Double free + mi_free(p[2]); + p[3] = mi_malloc(786456); + // [BUG] Found overlap + // p[3]=0x429b2ea2000 (size=917504), p[1]=0x429b2e42000 (size=786432) + fprintf(stderr, "p3: %p-%p, p1: %p-%p, p2: %p\n", p[3], (uint8_t*)(p[3]) + 786456, p[1], (uint8_t*)(p[1]) + 655362, p[2]); +} + +static void double_free2() { + void* p[256]; + //uintptr_t buf[256]; + // [INFO] Command buffer: 0x327b2000 + // [INFO] Input size: 182 + p[0] = malloc(712352); + p[1] = malloc(786432); + free(p[0]); + // [VULN] Double free + free(p[0]); + p[2] = malloc(786440); + p[3] = malloc(917504); + p[4] = malloc(786440); + // [BUG] Found overlap + // p[4]=0x433f1402000 (size=917504), p[1]=0x433f14c2000 (size=786432) + fprintf(stderr, "p1: %p-%p, p2: %p-%p\n", p[4], (uint8_t*)(p[4]) + 917504, p[1], (uint8_t*)(p[1]) + 786432); +} + + +// Try to corrupt the heap through buffer overflow +#define N 256 +#define SZ 64 + +static void corrupt_free() { + void* p[N]; + // allocate + for (int i = 0; i < N; i++) { + p[i] = malloc(SZ); + } + // free some + for (int i = 0; i < N; i += (N/10)) { + free(p[i]); + p[i] = NULL; + } + // try to corrupt the free list + for (int i = 0; i < N; i++) { + if (p[i] != NULL) { + memset(p[i], 0, SZ+8); + } + } + // allocate more.. trying to trigger an allocation from a corrupted entry + // this may need many allocations to get there (if at all) + for (int i = 0; i < 4096; i++) { + malloc(SZ); + } +} diff --git a/extlib/mimalloc/test/main-override.c b/extlib/mimalloc/test/main-override.c new file mode 100644 index 0000000..1bec117 --- /dev/null +++ b/extlib/mimalloc/test/main-override.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +#include + +int main() { + mi_version(); // ensure mimalloc library is linked + void* p1 = malloc(78); + void* p2 = malloc(24); + free(p1); + p1 = malloc(8); + //char* s = strdup("hello\n"); + free(p2); + p2 = malloc(16); + p1 = realloc(p1, 32); + free(p1); + free(p2); + //free(s); + //mi_collect(true); + + /* now test if override worked by allocating/freeing across the api's*/ + //p1 = mi_malloc(32); + //free(p1); + //p2 = malloc(32); + //mi_free(p2); + mi_stats_print(NULL); + return 0; +} diff --git a/extlib/mimalloc/test/main-override.cpp b/extlib/mimalloc/test/main-override.cpp new file mode 100644 index 0000000..16c4028 --- /dev/null +++ b/extlib/mimalloc/test/main-override.cpp @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#ifdef _WIN32 +#include +static void msleep(unsigned long msecs) { Sleep(msecs); } +#else +#include +static void msleep(unsigned long msecs) { usleep(msecs * 1000UL); } +#endif + +void heap_thread_free_large(); // issue #221 +void heap_no_delete(); // issue #202 +void heap_late_free(); // issue #204 +void padding_shrink(); // issue #209 +void various_tests(); +void test_mt_shutdown(); + +int main() { + mi_stats_reset(); // ignore earlier allocations + heap_thread_free_large(); + heap_no_delete(); + heap_late_free(); + padding_shrink(); + various_tests(); + //test_mt_shutdown(); + mi_stats_print(NULL); + return 0; +} + +static void* p = malloc(8); + +void free_p() { + free(p); + return; +} + +class Test { +private: + int i; +public: + Test(int x) { i = x; } + ~Test() { } +}; + + +void various_tests() { + atexit(free_p); + void* p1 = malloc(78); + void* p2 = mi_malloc_aligned(16, 24); + free(p1); + p1 = malloc(8); + char* s = mi_strdup("hello\n"); + + //char* s = _strdup("hello\n"); + //char* buf = NULL; + //size_t len; + //_dupenv_s(&buf,&len,"MIMALLOC_VERBOSE"); + //mi_free(buf); + + mi_free(p2); + p2 = malloc(16); + p1 = realloc(p1, 32); + free(p1); + free(p2); + mi_free(s); + Test* t = new Test(42); + delete t; + t = new (std::nothrow) Test(42); + delete t; +} + +class Static { +private: + void* p; +public: + Static() { + p = malloc(64); + return; + } + ~Static() { + free(p); + return; + } +}; + +static Static s = Static(); + + +bool test_stl_allocator1() { + std::vector > vec; + vec.push_back(1); + vec.pop_back(); + return vec.size() == 0; +} + +struct some_struct { int i; int j; double z; }; + +bool test_stl_allocator2() { + std::vector > vec; + vec.push_back(some_struct()); + vec.pop_back(); + return vec.size() == 0; +} + + + +// Issue #202 +void heap_no_delete_worker() { + mi_heap_t* heap = mi_heap_new(); + void* q = mi_heap_malloc(heap, 1024); + // mi_heap_delete(heap); // uncomment to prevent assertion +} + +void heap_no_delete() { + auto t1 = std::thread(heap_no_delete_worker); + t1.join(); +} + + +// Issue #204 +volatile void* global_p; + +void t1main() { + mi_heap_t* heap = mi_heap_new(); + global_p = mi_heap_malloc(heap, 1024); + mi_heap_delete(heap); +} + +void heap_late_free() { + auto t1 = std::thread(t1main); + + msleep(2000); + assert(global_p); + mi_free((void*)global_p); + + t1.join(); +} + +// issue #209 +static void* shared_p; +static void alloc0(/* void* arg */) +{ + shared_p = mi_malloc(8); +} + +void padding_shrink(void) +{ + auto t1 = std::thread(alloc0); + t1.join(); + mi_free(shared_p); +} + + +// Issue #221 +void heap_thread_free_large_worker() { + mi_free(shared_p); +} + +void heap_thread_free_large() { + for (int i = 0; i < 100; i++) { + shared_p = mi_malloc_aligned(2*1024*1024 + 1, 8); + auto t1 = std::thread(heap_thread_free_large_worker); + t1.join(); + } +} + + + +void test_mt_shutdown() +{ + const int threads = 5; + std::vector< std::future< std::vector< char* > > > ts; + + auto fn = [&]() + { + std::vector< char* > ps; + ps.reserve(1000); + for (int i = 0; i < 1000; i++) + ps.emplace_back(new char[1]); + return ps; + }; + + for (int i = 0; i < threads; i++) + ts.emplace_back(std::async(std::launch::async, fn)); + + for (auto& f : ts) + for (auto& p : f.get()) + delete[] p; + + std::cout << "done" << std::endl; +} diff --git a/extlib/mimalloc/test/main.c b/extlib/mimalloc/test/main.c new file mode 100644 index 0000000..b148f71 --- /dev/null +++ b/extlib/mimalloc/test/main.c @@ -0,0 +1,46 @@ +#include +#include +#include + +void test_heap(void* p_out) { + mi_heap_t* heap = mi_heap_new(); + void* p1 = mi_heap_malloc(heap,32); + void* p2 = mi_heap_malloc(heap,48); + mi_free(p_out); + mi_heap_destroy(heap); + //mi_heap_delete(heap); mi_free(p1); mi_free(p2); +} + +void test_large() { + const size_t N = 1000; + + for (size_t i = 0; i < N; ++i) { + size_t sz = 1ull << 21; + char* a = mi_mallocn_tp(char,sz); + for (size_t k = 0; k < sz; k++) { a[k] = 'x'; } + mi_free(a); + } +} + +int main() { + void* p1 = mi_malloc(16); + void* p2 = mi_malloc(1000000); + mi_free(p1); + mi_free(p2); + p1 = mi_malloc(16); + p2 = mi_malloc(16); + mi_free(p1); + mi_free(p2); + + test_heap(mi_malloc(32)); + + p1 = mi_malloc_aligned(64, 16); + p2 = mi_malloc_aligned(160,24); + mi_free(p2); + mi_free(p1); + //test_large(); + + mi_collect(true); + mi_stats_print(NULL); + return 0; +} diff --git a/extlib/mimalloc/test/readme.md b/extlib/mimalloc/test/readme.md new file mode 100644 index 0000000..db3524c --- /dev/null +++ b/extlib/mimalloc/test/readme.md @@ -0,0 +1,16 @@ +Testing allocators is difficult as bugs may only surface after particular +allocation patterns. The main approach to testing _mimalloc_ is therefore +to have extensive internal invariant checking (see `page_is_valid` in `page.c` +for example), which is enabled in debug mode with `-DMI_DEBUG_FULL=ON`. +The main testing strategy is then to run [`mimalloc-bench`][bench] using full +invariant checking to catch any potential problems over a wide range of intensive +allocation benchmarks and programs. + +However, this does not test well for the entire API surface and this is tested +with `test-api.c` when using `make test` (from `out/debug` etc). (This is +not complete yet, please add to it.) + +The `main.c` and `main-override.c` are there to test if building and overriding +from a local install works and therefore these build a separate `test/CMakeLists.txt`. + +[bench]: https://github.com/daanx/mimalloc-bench diff --git a/extlib/mimalloc/test/test-api.c b/extlib/mimalloc/test/test-api.c new file mode 100644 index 0000000..e5827a9 --- /dev/null +++ b/extlib/mimalloc/test/test-api.c @@ -0,0 +1,249 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* +Testing allocators is difficult as bugs may only surface after particular +allocation patterns. The main approach to testing _mimalloc_ is therefore +to have extensive internal invariant checking (see `page_is_valid` in `page.c` +for example), which is enabled in debug mode with `-DMI_DEBUG_FULL=ON`. +The main testing is then to run `mimalloc-bench` [1] using full invariant checking +to catch any potential problems over a wide range of intensive allocation bench +marks. + +However, this does not test well for the entire API surface. In this test file +we therefore test the API over various inputs. Please add more tests :-) + +[1] https://github.com/daanx/mimalloc-bench +*/ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +#include +#endif + +#include "mimalloc.h" +// #include "mimalloc-internal.h" + +// --------------------------------------------------------------------------- +// Test macros: CHECK(name,predicate) and CHECK_BODY(name,body) +// --------------------------------------------------------------------------- +static int ok = 0; +static int failed = 0; + +#define CHECK_BODY(name,body) \ + do { \ + fprintf(stderr,"test: %s... ", name ); \ + bool result = true; \ + do { body } while(false); \ + if (!(result)) { \ + failed++; \ + fprintf(stderr, \ + "\n FAILED: %s:%d:\n %s\n", \ + __FILE__, \ + __LINE__, \ + #body); \ + /* exit(1); */ \ + } \ + else { \ + ok++; \ + fprintf(stderr,"ok.\n"); \ + } \ + } while (false) + +#define CHECK(name,expr) CHECK_BODY(name,{ result = (expr); }) + +// --------------------------------------------------------------------------- +// Test functions +// --------------------------------------------------------------------------- +bool test_heap1(); +bool test_heap2(); +bool test_stl_allocator1(); +bool test_stl_allocator2(); + +// --------------------------------------------------------------------------- +// Main testing +// --------------------------------------------------------------------------- +int main() { + mi_option_disable(mi_option_verbose); + + // --------------------------------------------------- + // Malloc + // --------------------------------------------------- + + CHECK_BODY("malloc-zero",{ + void* p = mi_malloc(0); mi_free(p); + }); + CHECK_BODY("malloc-nomem1",{ + result = (mi_malloc(SIZE_MAX/2) == NULL); + }); + CHECK_BODY("malloc-null",{ + mi_free(NULL); + }); + CHECK_BODY("calloc-overflow",{ + // use (size_t)&mi_calloc to get some number without triggering compiler warnings + result = (mi_calloc((size_t)&mi_calloc,SIZE_MAX/1000) == NULL); + }); + CHECK_BODY("calloc0",{ + result = (mi_usable_size(mi_calloc(0,1000)) <= 16); + }); + + // --------------------------------------------------- + // Extended + // --------------------------------------------------- + CHECK_BODY("posix_memalign1", { + void* p = &p; + int err = mi_posix_memalign(&p, sizeof(void*), 32); + result = ((err==0 && (uintptr_t)p % sizeof(void*) == 0) || p==&p); + mi_free(p); + }); + CHECK_BODY("posix_memalign_no_align", { + void* p = &p; + int err = mi_posix_memalign(&p, 3, 32); + result = (err==EINVAL && p==&p); + }); + CHECK_BODY("posix_memalign_zero", { + void* p = &p; + int err = mi_posix_memalign(&p, sizeof(void*), 0); + mi_free(p); + result = (err==0); + }); + CHECK_BODY("posix_memalign_nopow2", { + void* p = &p; + int err = mi_posix_memalign(&p, 3*sizeof(void*), 32); + result = (err==EINVAL && p==&p); + }); + CHECK_BODY("posix_memalign_nomem", { + void* p = &p; + int err = mi_posix_memalign(&p, sizeof(void*), SIZE_MAX); + result = (err==ENOMEM && p==&p); + }); + + // --------------------------------------------------- + // Aligned API + // --------------------------------------------------- + CHECK_BODY("malloc-aligned1", { + void* p = mi_malloc_aligned(32,32); result = (p != NULL && (uintptr_t)(p) % 32 == 0); mi_free(p); + }); + CHECK_BODY("malloc-aligned2", { + void* p = mi_malloc_aligned(48,32); result = (p != NULL && (uintptr_t)(p) % 32 == 0); mi_free(p); + }); + CHECK_BODY("malloc-aligned3", { + void* p1 = mi_malloc_aligned(48,32); bool result1 = (p1 != NULL && (uintptr_t)(p1) % 32 == 0); + void* p2 = mi_malloc_aligned(48,32); bool result2 = (p2 != NULL && (uintptr_t)(p2) % 32 == 0); + mi_free(p2); + mi_free(p1); + result = (result1&&result2); + }); + CHECK_BODY("malloc-aligned4", { + void* p; + bool ok = true; + for (int i = 0; i < 8 && ok; i++) { + p = mi_malloc_aligned(8, 16); + ok = (p != NULL && (uintptr_t)(p) % 16 == 0); mi_free(p); + } + result = ok; + }); + CHECK_BODY("malloc-aligned5", { + void* p = mi_malloc_aligned(4097,4096); size_t usable = mi_usable_size(p); result = usable >= 4097 && usable < 10000; mi_free(p); + }); + CHECK_BODY("malloc-aligned-at1", { + void* p = mi_malloc_aligned_at(48,32,0); result = (p != NULL && ((uintptr_t)(p) + 0) % 32 == 0); mi_free(p); + }); + CHECK_BODY("malloc-aligned-at2", { + void* p = mi_malloc_aligned_at(50,32,8); result = (p != NULL && ((uintptr_t)(p) + 8) % 32 == 0); mi_free(p); + }); + CHECK_BODY("memalign1", { + void* p; + bool ok = true; + for (int i = 0; i < 8 && ok; i++) { + p = mi_memalign(16,8); + ok = (p != NULL && (uintptr_t)(p) % 16 == 0); mi_free(p); + } + result = ok; + }); + + // --------------------------------------------------- + // Heaps + // --------------------------------------------------- + CHECK("heap_destroy", test_heap1()); + CHECK("heap_delete", test_heap2()); + + //mi_stats_print(NULL); + + // --------------------------------------------------- + // various + // --------------------------------------------------- + CHECK_BODY("realpath", { + char* s = mi_realpath( ".", NULL ); + // printf("realpath: %s\n",s); + mi_free(s); + }); + + CHECK("stl_allocator1", test_stl_allocator1()); + CHECK("stl_allocator2", test_stl_allocator2()); + + // --------------------------------------------------- + // Done + // ---------------------------------------------------[] + fprintf(stderr,"\n\n---------------------------------------------\n" + "succeeded: %i\n" + "failed : %i\n\n", ok, failed); + return failed; +} + +// --------------------------------------------------- +// Larger test functions +// --------------------------------------------------- + +bool test_heap1() { + mi_heap_t* heap = mi_heap_new(); + int* p1 = mi_heap_malloc_tp(heap,int); + int* p2 = mi_heap_malloc_tp(heap,int); + *p1 = *p2 = 43; + mi_heap_destroy(heap); + return true; +} + +bool test_heap2() { + mi_heap_t* heap = mi_heap_new(); + int* p1 = mi_heap_malloc_tp(heap,int); + int* p2 = mi_heap_malloc_tp(heap,int); + mi_heap_delete(heap); + *p1 = 42; + mi_free(p1); + mi_free(p2); + return true; +} + +bool test_stl_allocator1() { +#ifdef __cplusplus + std::vector > vec; + vec.push_back(1); + vec.pop_back(); + return vec.size() == 0; +#else + return true; +#endif +} + +struct some_struct { int i; int j; double z; }; + +bool test_stl_allocator2() { +#ifdef __cplusplus + std::vector > vec; + vec.push_back(some_struct()); + vec.pop_back(); + return vec.size() == 0; +#else + return true; +#endif +} diff --git a/extlib/mimalloc/test/test-stress.c b/extlib/mimalloc/test/test-stress.c new file mode 100644 index 0000000..7d8993a --- /dev/null +++ b/extlib/mimalloc/test/test-stress.c @@ -0,0 +1,329 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018,2019 Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. +-----------------------------------------------------------------------------*/ + +/* This is a stress test for the allocator, using multiple threads and + transferring objects between threads. It tries to reflect real-world workloads: + - allocation size is distributed linearly in powers of two + - with some fraction extra large (and some extra extra large) + - the allocations are initialized and read again at free + - pointers transfer between threads + - threads are terminated and recreated with some objects surviving in between + - uses deterministic "randomness", but execution can still depend on + (random) thread scheduling. Do not use this test as a benchmark! +*/ + +#include +#include +#include +#include +#include +#include + +// > mimalloc-test-stress [THREADS] [SCALE] [ITER] +// +// argument defaults +static int THREADS = 32; // more repeatable if THREADS <= #processors +static int SCALE = 10; // scaling factor +static int ITER = 50; // N full iterations destructing and re-creating all threads + +// static int THREADS = 8; // more repeatable if THREADS <= #processors +// static int SCALE = 100; // scaling factor + +#define STRESS // undefine for leak test + +static bool allow_large_objects = true; // allow very large objects? +static size_t use_one_size = 0; // use single object size of `N * sizeof(uintptr_t)`? + + +#ifdef USE_STD_MALLOC +#define custom_calloc(n,s) calloc(n,s) +#define custom_realloc(p,s) realloc(p,s) +#define custom_free(p) free(p) +#else +#define custom_calloc(n,s) mi_calloc(n,s) +#define custom_realloc(p,s) mi_realloc(p,s) +#define custom_free(p) mi_free(p) +#endif + +// transfer pointer between threads +#define TRANSFERS (1000) +static volatile void* transfer[TRANSFERS]; + + +#if (UINTPTR_MAX != UINT32_MAX) +const uintptr_t cookie = 0xbf58476d1ce4e5b9UL; +#else +const uintptr_t cookie = 0x1ce4e5b9UL; +#endif + +static void* atomic_exchange_ptr(volatile void** p, void* newval); + +typedef uintptr_t* random_t; + +static uintptr_t pick(random_t r) { + uintptr_t x = *r; +#if (UINTPTR_MAX > UINT32_MAX) + // by Sebastiano Vigna, see: + x ^= x >> 30; + x *= 0xbf58476d1ce4e5b9UL; + x ^= x >> 27; + x *= 0x94d049bb133111ebUL; + x ^= x >> 31; +#else + // by Chris Wellons, see: + x ^= x >> 16; + x *= 0x7feb352dUL; + x ^= x >> 15; + x *= 0x846ca68bUL; + x ^= x >> 16; +#endif + *r = x; + return x; +} + +static bool chance(size_t perc, random_t r) { + return (pick(r) % 100 <= perc); +} + +static void* alloc_items(size_t items, random_t r) { + if (chance(1, r)) { + if (chance(1, r) && allow_large_objects) items *= 10000; // 0.01% giant + else if (chance(10, r) && allow_large_objects) items *= 1000; // 0.1% huge + else items *= 100; // 1% large objects; + } + if (items == 40) items++; // pthreads uses that size for stack increases + if (use_one_size > 0) items = (use_one_size / sizeof(uintptr_t)); + if (items==0) items = 1; + uintptr_t* p = (uintptr_t*)custom_calloc(items,sizeof(uintptr_t)); + if (p != NULL) { + for (uintptr_t i = 0; i < items; i++) { + p[i] = (items - i) ^ cookie; + } + } + return p; +} + +static void free_items(void* p) { + if (p != NULL) { + uintptr_t* q = (uintptr_t*)p; + uintptr_t items = (q[0] ^ cookie); + for (uintptr_t i = 0; i < items; i++) { + if ((q[i] ^ cookie) != items - i) { + fprintf(stderr, "memory corruption at block %p at %zu\n", p, i); + abort(); + } + } + } + custom_free(p); +} + + +static void stress(intptr_t tid) { + //bench_start_thread(); + uintptr_t r = (tid * 43); // rand(); + const size_t max_item_shift = 5; // 128 + const size_t max_item_retained_shift = max_item_shift + 2; + size_t allocs = 100 * ((size_t)SCALE) * (tid % 8 + 1); // some threads do more + size_t retain = allocs / 2; + void** data = NULL; + size_t data_size = 0; + size_t data_top = 0; + void** retained = (void**)custom_calloc(retain,sizeof(void*)); + size_t retain_top = 0; + + while (allocs > 0 || retain > 0) { + if (retain == 0 || (chance(50, &r) && allocs > 0)) { + // 50%+ alloc + allocs--; + if (data_top >= data_size) { + data_size += 100000; + data = (void**)custom_realloc(data, data_size * sizeof(void*)); + } + data[data_top++] = alloc_items(1ULL << (pick(&r) % max_item_shift), &r); + } + else { + // 25% retain + retained[retain_top++] = alloc_items( 1ULL << (pick(&r) % max_item_retained_shift), &r); + retain--; + } + if (chance(66, &r) && data_top > 0) { + // 66% free previous alloc + size_t idx = pick(&r) % data_top; + free_items(data[idx]); + data[idx] = NULL; + } + if (chance(25, &r) && data_top > 0) { + // 25% exchange a local pointer with the (shared) transfer buffer. + size_t data_idx = pick(&r) % data_top; + size_t transfer_idx = pick(&r) % TRANSFERS; + void* p = data[data_idx]; + void* q = atomic_exchange_ptr(&transfer[transfer_idx], p); + data[data_idx] = q; + } + } + // free everything that is left + for (size_t i = 0; i < retain_top; i++) { + free_items(retained[i]); + } + for (size_t i = 0; i < data_top; i++) { + free_items(data[i]); + } + custom_free(retained); + custom_free(data); + //bench_end_thread(); +} + +static void run_os_threads(size_t nthreads, void (*entry)(intptr_t tid)); + +static void test_stress(void) { + uintptr_t r = rand(); + for (int n = 0; n < ITER; n++) { + run_os_threads(THREADS, &stress); + for (int i = 0; i < TRANSFERS; i++) { + if (chance(50, &r) || n + 1 == ITER) { // free all on last run, otherwise free half of the transfers + void* p = atomic_exchange_ptr(&transfer[i], NULL); + free_items(p); + } + } + // mi_collect(false); +#ifndef NDEBUG + if ((n + 1) % 10 == 0) { printf("- iterations left: %3d\n", ITER - (n + 1)); } +#endif + } +} + +#ifndef STRESS +static void leak(intptr_t tid) { + uintptr_t r = rand(); + void* p = alloc_items(1 /*pick(&r)%128*/, &r); + if (chance(50, &r)) { + intptr_t i = (pick(&r) % TRANSFERS); + void* q = atomic_exchange_ptr(&transfer[i], p); + free_items(q); + } +} + +static void test_leak(void) { + for (int n = 0; n < ITER; n++) { + run_os_threads(THREADS, &leak); + mi_collect(false); +#ifndef NDEBUG + if ((n + 1) % 10 == 0) { printf("- iterations left: %3d\n", ITER - (n + 1)); } +#endif + } +} +#endif + +int main(int argc, char** argv) { + // > mimalloc-test-stress [THREADS] [SCALE] [ITER] + if (argc >= 2) { + char* end; + long n = strtol(argv[1], &end, 10); + if (n > 0) THREADS = n; + } + if (argc >= 3) { + char* end; + long n = (strtol(argv[2], &end, 10)); + if (n > 0) SCALE = n; + } + if (argc >= 4) { + char* end; + long n = (strtol(argv[3], &end, 10)); + if (n > 0) ITER = n; + } + printf("Using %d threads with a %d%% load-per-thread and %d iterations\n", THREADS, SCALE, ITER); + //int res = mi_reserve_huge_os_pages(4,1); + //printf("(reserve huge: %i\n)", res); + + //bench_start_program(); + + // Run ITER full iterations where half the objects in the transfer buffer survive to the next round. + srand(0x7feb352d); + // mi_stats_reset(); +#ifdef STRESS + test_stress(); +#else + test_leak(); +#endif + + // mi_collect(true); + mi_stats_print(NULL); + //bench_end_program(); + return 0; +} + + +static void (*thread_entry_fun)(intptr_t) = &stress; + +#ifdef _WIN32 + +#include + +static DWORD WINAPI thread_entry(LPVOID param) { + thread_entry_fun((intptr_t)param); + return 0; +} + +static void run_os_threads(size_t nthreads, void (*fun)(intptr_t)) { + thread_entry_fun = fun; + DWORD* tids = (DWORD*)custom_calloc(nthreads,sizeof(DWORD)); + HANDLE* thandles = (HANDLE*)custom_calloc(nthreads,sizeof(HANDLE)); + for (uintptr_t i = 0; i < nthreads; i++) { + thandles[i] = CreateThread(0, 4096, &thread_entry, (void*)(i), 0, &tids[i]); + } + for (size_t i = 0; i < nthreads; i++) { + WaitForSingleObject(thandles[i], INFINITE); + } + for (size_t i = 0; i < nthreads; i++) { + CloseHandle(thandles[i]); + } + custom_free(tids); + custom_free(thandles); +} + +static void* atomic_exchange_ptr(volatile void** p, void* newval) { +#if (INTPTR_MAX == INT32_MAX) + return (void*)InterlockedExchange((volatile LONG*)p, (LONG)newval); +#else + return (void*)InterlockedExchange64((volatile LONG64*)p, (LONG64)newval); +#endif +} +#else + +#include + +static void* thread_entry(void* param) { + thread_entry_fun((uintptr_t)param); + return NULL; +} + +static void run_os_threads(size_t nthreads, void (*fun)(intptr_t)) { + thread_entry_fun = fun; + pthread_t* threads = (pthread_t*)custom_calloc(nthreads,sizeof(pthread_t)); + memset(threads, 0, sizeof(pthread_t) * nthreads); + //pthread_setconcurrency(nthreads); + for (uintptr_t i = 0; i < nthreads; i++) { + pthread_create(&threads[i], NULL, &thread_entry, (void*)i); + } + for (size_t i = 0; i < nthreads; i++) { + pthread_join(threads[i], NULL); + } + custom_free(threads); +} + +#ifdef __cplusplus +#include +static void* atomic_exchange_ptr(volatile void** p, void* newval) { + return std::atomic_exchange((volatile std::atomic*)p, newval); +} +#else +#include +static void* atomic_exchange_ptr(volatile void** p, void* newval) { + return atomic_exchange((volatile _Atomic(void*)*)p, newval); +} +#endif + +#endif diff --git a/pkg/flatpak/.gitignore b/pkg/flatpak/.gitignore new file mode 100644 index 0000000..ca1b1d7 --- /dev/null +++ b/pkg/flatpak/.gitignore @@ -0,0 +1,4 @@ +/.flatpak-builder +/build-dir +/repo +*.flatpak diff --git a/pkg/flatpak/build.sh b/pkg/flatpak/build.sh new file mode 100755 index 0000000..a65961f --- /dev/null +++ b/pkg/flatpak/build.sh @@ -0,0 +1,4 @@ +#!/bin/sh -ex +cd $(dirname $0) +flatpak-builder "$@" --force-clean --repo repo build-dir com.solvespace.SolveSpace.json +flatpak build-bundle repo solvespace.flatpak com.solvespace.SolveSpace diff --git a/pkg/flatpak/com.solvespace.SolveSpace.json b/pkg/flatpak/com.solvespace.SolveSpace.json new file mode 100644 index 0000000..8c4ce51 --- /dev/null +++ b/pkg/flatpak/com.solvespace.SolveSpace.json @@ -0,0 +1,141 @@ +{ + "app-id": "com.solvespace.SolveSpace", + "runtime": "org.gnome.Platform", + "runtime-version": "3.30", + "sdk": "org.gnome.Sdk", + "finish-args": [ + /* Access to display server and OpenGL */ + "--share=ipc", "--socket=fallback-x11", "--socket=wayland", "--device=dri", + /* Access to save files */ + "--filesystem=home" + ], + "cleanup": [ + "/include", "/lib/*/include", + "*.a", "*.la", "*.m4", "/lib/libslvs*.so*", "/lib/libglibmm_generate_extra_defs*.so*", + "/share/pkgconfig", "*.pc", + "/share/man", "/share/doc", + "/share/aclocal", + /* mm-common junk */ + "/bin/mm-common-prepare", + "/share/mm-common" + ], + "command": "solvespace", + "modules": [ + { + "name": "mm-common", + "sources": [ + { + "type": "archive", + "url": "http://ftp.gnome.org/pub/GNOME/sources/mm-common/0.9/mm-common-0.9.12.tar.xz", + "sha256": "ceffdcce1e5b52742884c233ec604bf6fded12eea9da077ce7a62c02c87e7c0b" + } + ] + }, + { + "name": "sigc++", + "config-opts": [ + "--disable-documentation" + ], + "sources": [ + { + "type": "archive", + "url": "http://ftp.gnome.org/pub/GNOME/sources/libsigc++/2.10/libsigc++-2.10.1.tar.xz", + "sha256": "c9a25f26178c6cbb147f9904d8c533b5a5c5111a41ac2eb781eb734eea446003" + } + ] + }, + { + "name": "glibmm", + "config-opts": [ + "--disable-documentation" + ], + "sources": [ + { + "type": "archive", + "url": "http://ftp.gnome.org/pub/GNOME/sources/glibmm/2.58/glibmm-2.58.1.tar.xz", + "sha256": "6e5fe03bdf1e220eeffd543e017fd2fb15bcec9235f0ffd50674aff9362a85f0" + } + ] + }, + { + "name": "cairomm", + "config-opts": [ + "--disable-documentation" + ], + "sources": [ + { + "type": "archive", + "url": "http://ftp.gnome.org/pub/GNOME/sources/cairomm/1.12/cairomm-1.12.0.tar.xz", + "sha256": "a54ada8394a86182525c0762e6f50db6b9212a2109280d13ec6a0b29bfd1afe6" + } + ] + }, + { + "name": "pangomm", + "config-opts": [ + "--disable-documentation" + ], + "sources": [ + { + "type": "archive", + "url": "http://ftp.gnome.org/pub/GNOME/sources/pangomm/2.40/pangomm-2.40.2.tar.xz", + "sha256": "0a97aa72513db9088ca3034af923484108746dba146e98ed76842cf858322d05" + } + ] + }, + { + "name": "atkmm", + "config-opts": [ + "--disable-documentation" + ], + "sources": [ + { + "type": "archive", + "url": "http://ftp.gnome.org/pub/GNOME/sources/atkmm/2.28/atkmm-2.28.0.tar.xz", + "sha256": "4c4cfc917fd42d3879ce997b463428d6982affa0fb660cafcc0bc2d9afcedd3a" + } + ] + }, + { + "name": "gtkmm", + "config-opts": [ + "--disable-documentation" + ], + "sources": [ + { + "type": "archive", + "url": "http://ftp.gnome.org/pub/GNOME/sources/gtkmm/3.24/gtkmm-3.24.1.tar.xz", + "sha256": "ddfe42ed2458a20a34de252854bcf4b52d3f0c671c045f56b42aa27c7542d2fd" + } + ] + }, + { + "name": "libjson-c", + "sources": [ + { + "type": "archive", + "url": "https://s3.amazonaws.com/json-c_releases/releases/json-c-0.13.1-nodoc.tar.gz", + "sha256": "94a26340c0785fcff4f46ff38609cf84ebcd670df0c8efd75d039cc951d80132" + } + ], + "buildsystem": "cmake", + "builddir": true + }, + { + "name": "SolveSpace", + "sources": [ + { + "type": "git", + "path": "/home/whitequark/Projects/solvespace" + } + ], + "buildsystem": "cmake", + "builddir": true, + "config-opts": [ + "-DFLATPAK=ON", + "-DENABLE_CLI=OFF", + "-DENABLE_TESTS=OFF" + ] + } + ] +} diff --git a/pkg/snap/.gitignore b/pkg/snap/.gitignore new file mode 100644 index 0000000..9f938db --- /dev/null +++ b/pkg/snap/.gitignore @@ -0,0 +1,3 @@ +*.snap +solvespace-snap-src +squashfs-root diff --git a/pkg/snap/build.sh b/pkg/snap/build.sh new file mode 100755 index 0000000..abd7a86 --- /dev/null +++ b/pkg/snap/build.sh @@ -0,0 +1,12 @@ +#!/bin/sh -xe + +dir="$(dirname "$(readlink -f "$0")")" +solvespace_snap_src="$dir/solvespace-snap-src" +trap "rm -rf $solvespace_snap_src" EXIT + +cd "$dir" + +git_root="$(git rev-parse --show-toplevel)" +rsync --filter=":- .gitignore" -r "$git_root"/ "$solvespace_snap_src" + +snapcraft "$@" diff --git a/pkg/snap/snap/snapcraft.yaml b/pkg/snap/snap/snapcraft.yaml new file mode 100644 index 0000000..400ea19 --- /dev/null +++ b/pkg/snap/snap/snapcraft.yaml @@ -0,0 +1,80 @@ +name: solvespace +base: core18 +summary: Parametric 2d/3d CAD +adopt-info: solvespace +description: | + SOLVESPACE is a free (GPLv3) parametric 3d CAD tool. + Applications include + * modeling 3d parts — draw with extrudes, revolves, and Boolean (union / difference) operations + * modeling 2d parts — draw the part as a single section, and export DXF, PDF, SVG; use 3d assembly to verify fit + * 3d-printed parts — export the STL or other triangle mesh expected by most 3d printers + * preparing CAM data — export 2d vector art for a waterjet machine or laser cutter; or generate STEP or STL, for import into third-party CAM software for machining + * mechanism design — use the constraint solver to simulate planar or spatial linkages, with pin, ball, or slide joints + * plane and solid geometry — replace hand-solved trigonometry and spreadsheets with a live dimensioned drawing + +confinement: strict +license: GPL-3.0 + +layout: + /usr/share/solvespace: + symlink: $SNAP/usr/share/solvespace + +apps: + solvespace: + command: usr/bin/solvespace + desktop: solvespace.desktop + extensions: [gnome-3-34] + plugs: [opengl, unity7, home, removable-media, gsettings, network] + environment: + __EGL_VENDOR_LIBRARY_DIRS: $SNAP/gnome-platform/usr/share/glvnd/egl_vendor.d:$SNAP/usr/share/glvnd/egl_vendor.d + cli: + command: usr/bin/solvespace-cli + extensions: [gnome-3-34] + plugs: [home, removable-media, network] + +parts: + solvespace: + plugin: cmake + source: ./solvespace-snap-src + source-type: local + override-pull: | + snapcraftctl pull + version_major=$(grep "solvespace_VERSION_MAJOR" CMakeLists.txt | tr -d "()" | cut -d" " -f2) + version_minor=$(grep "solvespace_VERSION_MINOR" CMakeLists.txt | tr -d "()" | cut -d" " -f2) + version="$version_major.$version_minor~$(git rev-parse --short=8 HEAD)" + snapcraftctl set-version "$version" + git describe --exact-match HEAD && grade="stable" || grade="devel" + snapcraftctl set-grade "$grade" + git submodule update --init extlib/libdxfrw extlib/flatbuffers extlib/q3d extlib/mimalloc + configflags: + - -DCMAKE_INSTALL_PREFIX=/usr + - -DCMAKE_BUILD_TYPE=Release + - -DENABLE_TESTS=OFF + - -DSNAP=ON + - -DENABLE_OPENMP=ON + - -DENABLE_LTO=ON + build-packages: + - zlib1g-dev + - libpng-dev + - libfreetype6-dev + - libjson-c-dev + - libgl-dev + - libsigc++-2.0-dev + - libspnav-dev + - git + - g++ + stage-packages: + - libspnav0 + - libsigc++-2.0-0v5 + + cleanup: + after: [solvespace] + plugin: nil + build-snaps: [core18, gnome-3-34-1804] + override-prime: | + # Remove all files from snap that are already included in the base snap or in + # any connected content snaps + set -eux + for snap in "core18" "gnome-3-34-1804"; do # List all content-snaps and base snaps you're using here + cd "/snap/$snap/current" && find . -type f,l -exec rm -f "$SNAPCRAFT_PRIME/{}" \; + done diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt new file mode 100644 index 0000000..5969039 --- /dev/null +++ b/res/CMakeLists.txt @@ -0,0 +1,294 @@ +# First, set up registration functions for the kinds of resources we handle. +set(resource_root ${CMAKE_CURRENT_SOURCE_DIR}/) +set(resource_list) +if(WIN32) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/win32/versioninfo.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/win32/versioninfo.rc) + + set(rc_file ${CMAKE_CURRENT_BINARY_DIR}/resources.rc) + file(WRITE ${rc_file} "// Autogenerated; do not edit\n") + file(APPEND ${rc_file} "#include \n") + file(APPEND ${rc_file} "#include \"${CMAKE_CURRENT_BINARY_DIR}/win32/versioninfo.rc\"\n") + + function(add_resource name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + + if(${ARGC} GREATER 1) + set(id ${ARGV1}) + else() + string(REPLACE ${resource_root} "" id ${source}) + endif() + if(${ARGC} GREATER 2) + set(type ${ARGV2}) + else() + set(type RCDATA) + endif() + file(SHA512 "${source}" hash) + file(APPEND ${rc_file} "${id} ${type} \"${source}\" // ${hash}\n") + # CMake doesn't track file dependencies across directories, so we force + # a reconfigure (which changes the RC file because of the hash above) + # every time a resource is changed. + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${source}") + endfunction() +elseif(APPLE) + set(app_resource_dir ${CMAKE_BINARY_DIR}/bin/SolveSpace.app/Contents/Resources) + set(cli_resource_dir ${CMAKE_BINARY_DIR}/res) + + function(add_resource name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + set(target_app ${app_resource_dir}/${name}) + set(target_cli ${cli_resource_dir}/${name}) + set(resource_list "${resource_list};${target_app};${target_cli}" PARENT_SCOPE) + + get_filename_component(target_app_dir ${target_app} DIRECTORY) + get_filename_component(target_cli_dir ${target_cli} DIRECTORY) + add_custom_command( + OUTPUT ${target_app} ${target_cli} + COMMAND ${CMAKE_COMMAND} -E make_directory ${target_app_dir} + COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target_app} + COMMAND ${CMAKE_COMMAND} -E make_directory ${target_cli_dir} + COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target_cli} + COMMENT "Copying resource ${name}" + DEPENDS ${source} + VERBATIM) + endfunction() + + function(add_xib name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + get_filename_component(basename ${name} NAME_WE) + set(target ${app_resource_dir}/${basename}.nib) + set(resource_list "${resource_list};${target}" PARENT_SCOPE) + + add_custom_command( + OUTPUT ${target} + COMMAND ${CMAKE_COMMAND} -E make_directory ${app_resource_dir} + COMMAND ibtool --errors --warnings --notices --output-format human-readable-text + --compile ${target} ${source} + COMMENT "Building Interface Builder file ${name}" + DEPENDS ${source} + VERBATIM) + endfunction() + + function(add_iconset name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + get_filename_component(basename ${name} NAME_WE) + set(target ${app_resource_dir}/${basename}.icns) + set(resource_list "${resource_list};${target}" PARENT_SCOPE) + + add_custom_command( + OUTPUT ${target} + COMMAND ${CMAKE_COMMAND} -E make_directory ${app_resource_dir} + COMMAND iconutil -c icns -o ${target} ${source} + COMMENT "Building icon set ${name}" + DEPENDS ${source} + VERBATIM) + endfunction() +else() # Unix + include(GNUInstallDirs) + + set(app_resource_dir ${CMAKE_BINARY_DIR}/res) + + function(add_resource name) + set(source ${CMAKE_CURRENT_SOURCE_DIR}/${name}) + set(target ${app_resource_dir}/${name}) + set(resource_list "${resource_list};${target}" PARENT_SCOPE) + + get_filename_component(target_dir ${target} DIRECTORY) + add_custom_command( + OUTPUT ${target} + COMMAND ${CMAKE_COMMAND} -E make_directory ${target_dir} + COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target} + COMMENT "Copying resource ${name}" + DEPENDS ${source} + VERBATIM) + + get_filename_component(name_dir ${name} DIRECTORY) + install(FILES ${source} + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/solvespace/${name_dir}) + endfunction() +endif() + +function(add_resources) + foreach(name ${ARGN}) + add_resource(${name}) + set(resource_list "${resource_list}" PARENT_SCOPE) + endforeach() +endfunction() + +# Second, register all resources. +if(WIN32) + add_resource(win32/icon.ico 4000 ICON) + add_resource(win32/manifest.xml 1 RT_MANIFEST) +elseif(APPLE) + add_iconset (cocoa/AppIcon.iconset) + add_xib (cocoa/MainMenu.xib) + add_xib (cocoa/SaveFormatAccessory.xib) +else() + add_resource(freedesktop/solvespace-48x48.png) + + if(FLATPAK) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/freedesktop/solvespace-flatpak.desktop.in + ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/solvespace-flatpak.desktop) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/solvespace-flatpak.desktop + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications + RENAME com.solvespace.SolveSpace.desktop) + + install(FILES freedesktop/solvespace-flatpak-mime.xml + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mime/packages + RENAME com.solvespace.SolveSpace-slvs.xml) + + install(FILES freedesktop/solvespace-scalable.svg + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps + RENAME com.solvespace.SolveSpace.svg) + install(FILES freedesktop/solvespace-scalable.svg + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/mimetypes + RENAME com.solvespace.SolveSpace.svg) + + foreach(SIZE 16x16 24x24 32x32 48x48) + install(FILES freedesktop/solvespace-${SIZE}.png + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}/apps + RENAME com.solvespace.SolveSpace.png) + install(FILES freedesktop/solvespace-${SIZE}.png + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}/mimetypes + RENAME com.solvespace.SolveSpace.png) + endforeach() + elseif(SNAP) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/freedesktop/solvespace-snap.desktop + DESTINATION / + RENAME solvespace.desktop) + + # snapd does not support registering new mime types + + install(FILES freedesktop/solvespace-scalable.svg + DESTINATION /meta/icons/hicolor/scalable/apps + RENAME snap.solvespace.svg) + + foreach(SIZE 16x16 24x24 32x32 48x48) + install(FILES freedesktop/solvespace-${SIZE}.png + DESTINATION /meta/icons/hicolor/${SIZE}/apps + RENAME snap.solvespace.png) + endforeach() + else() + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/freedesktop/solvespace.desktop.in + ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/solvespace.desktop) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/freedesktop/solvespace.desktop + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications) + + install(FILES freedesktop/solvespace-mime.xml + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mime/packages + RENAME solvespace-slvs.xml) + + install(FILES freedesktop/solvespace-scalable.svg + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps + RENAME solvespace.svg) + install(FILES freedesktop/solvespace-scalable.svg + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/mimetypes + RENAME application.x-solvespace.svg) + + foreach(SIZE 16x16 24x24 32x32 48x48) + install(FILES freedesktop/solvespace-${SIZE}.png + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}/apps + RENAME solvespace.png) + install(FILES freedesktop/solvespace-${SIZE}.png + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}/mimetypes + RENAME application.x-solvespace.png) + endforeach() + + foreach(SIZE 16x16 24x24 32x32 48x48) + install(FILES freedesktop/solvespace-${SIZE}.xpm + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pixmaps) + endforeach() + endif() +endif() + +add_resources( + banner.txt + icons/graphics-window/angle.png + icons/graphics-window/arc.png + icons/graphics-window/assemble.png + icons/graphics-window/bezier.png + icons/graphics-window/circle.png + icons/graphics-window/construction.png + icons/graphics-window/equal.png + icons/graphics-window/extrude.png + icons/graphics-window/horiz.png + icons/graphics-window/image.png + icons/graphics-window/in3d.png + icons/graphics-window/lathe.png + icons/graphics-window/length.png + icons/graphics-window/line.png + icons/graphics-window/ontoworkplane.png + icons/graphics-window/other-supp.png + icons/graphics-window/parallel.png + icons/graphics-window/perpendicular.png + icons/graphics-window/pointonx.png + icons/graphics-window/point.png + icons/graphics-window/rectangle.png + icons/graphics-window/ref.png + icons/graphics-window/same-orientation.png + icons/graphics-window/sketch-in-3d.png + icons/graphics-window/sketch-in-plane.png + icons/graphics-window/step-rotate.png + icons/graphics-window/step-translate.png + icons/graphics-window/symmetric.png + icons/graphics-window/tangent-arc.png + icons/graphics-window/text.png + icons/graphics-window/trim.png + icons/graphics-window/vert.png + icons/text-window/constraint.png + icons/text-window/construction.png + icons/text-window/edges.png + icons/text-window/faces.png + icons/text-window/occluded-visible.png + icons/text-window/occluded-stippled.png + icons/text-window/occluded-invisible.png + icons/text-window/mesh.png + icons/text-window/normal.png + icons/text-window/outlines.png + icons/text-window/point.png + icons/text-window/shaded.png + icons/text-window/workplane.png + locales.txt + locales/de_DE.po + locales/en_US.po + locales/fr_FR.po + locales/uk_UA.po + locales/ru_RU.po + locales/zh_CN.po + fonts/unifont.hex.gz + fonts/private/0-check-false.png + fonts/private/1-check-true.png + fonts/private/2-radio-false.png + fonts/private/3-radio-true.png + fonts/private/4-stipple-dot.png + fonts/private/5-stipple-dash-long.png + fonts/private/6-stipple-dash.png + fonts/private/7-stipple-zigzag.png + fonts/unicode.lff.gz + fonts/BitstreamVeraSans-Roman-builtin.ttf + shaders/imesh.frag + shaders/imesh.vert + shaders/imesh_point.frag + shaders/imesh_point.vert + shaders/imesh_tex.frag + shaders/imesh_texa.frag + shaders/imesh_tex.vert + shaders/mesh.frag + shaders/mesh.vert + shaders/mesh_fill.frag + shaders/mesh_fill.vert + shaders/edge.frag + shaders/edge.vert + shaders/outline.vert + threejs/three-r76.js.gz + threejs/hammer-2.0.8.js.gz + threejs/SolveSpaceControls.js) + +# Third, distribute the resources. +add_custom_target(resources + DEPENDS ${resource_list}) +if(WIN32) + set_property(TARGET resources PROPERTY EXTRA_SOURCES ${rc_file}) +endif() diff --git a/res/banner.txt b/res/banner.txt new file mode 100644 index 0000000..ad8f4f8 --- /dev/null +++ b/res/banner.txt @@ -0,0 +1 @@ +SolveSpace! diff --git a/src/cocoa/AppIcon.iconset/icon_128x128.png b/res/cocoa/AppIcon.iconset/icon_128x128.png similarity index 100% rename from src/cocoa/AppIcon.iconset/icon_128x128.png rename to res/cocoa/AppIcon.iconset/icon_128x128.png diff --git a/src/cocoa/AppIcon.iconset/icon_128x128@2x.png b/res/cocoa/AppIcon.iconset/icon_128x128@2x.png similarity index 100% rename from src/cocoa/AppIcon.iconset/icon_128x128@2x.png rename to res/cocoa/AppIcon.iconset/icon_128x128@2x.png diff --git a/src/cocoa/AppIcon.iconset/icon_16x16.png b/res/cocoa/AppIcon.iconset/icon_16x16.png similarity index 100% rename from src/cocoa/AppIcon.iconset/icon_16x16.png rename to res/cocoa/AppIcon.iconset/icon_16x16.png diff --git a/src/cocoa/AppIcon.iconset/icon_16x16@2x.png b/res/cocoa/AppIcon.iconset/icon_16x16@2x.png similarity index 100% rename from src/cocoa/AppIcon.iconset/icon_16x16@2x.png rename to res/cocoa/AppIcon.iconset/icon_16x16@2x.png diff --git a/src/cocoa/AppIcon.iconset/icon_256x256.png b/res/cocoa/AppIcon.iconset/icon_256x256.png similarity index 100% rename from src/cocoa/AppIcon.iconset/icon_256x256.png rename to res/cocoa/AppIcon.iconset/icon_256x256.png diff --git a/src/cocoa/AppIcon.iconset/icon_256x256@2x.png b/res/cocoa/AppIcon.iconset/icon_256x256@2x.png similarity index 100% rename from src/cocoa/AppIcon.iconset/icon_256x256@2x.png rename to res/cocoa/AppIcon.iconset/icon_256x256@2x.png diff --git a/src/cocoa/AppIcon.iconset/icon_32x32.png b/res/cocoa/AppIcon.iconset/icon_32x32.png similarity index 100% rename from src/cocoa/AppIcon.iconset/icon_32x32.png rename to res/cocoa/AppIcon.iconset/icon_32x32.png diff --git a/src/cocoa/AppIcon.iconset/icon_32x32@2x.png b/res/cocoa/AppIcon.iconset/icon_32x32@2x.png similarity index 100% rename from src/cocoa/AppIcon.iconset/icon_32x32@2x.png rename to res/cocoa/AppIcon.iconset/icon_32x32@2x.png diff --git a/src/cocoa/AppIcon.iconset/icon_512x512.png b/res/cocoa/AppIcon.iconset/icon_512x512.png similarity index 100% rename from src/cocoa/AppIcon.iconset/icon_512x512.png rename to res/cocoa/AppIcon.iconset/icon_512x512.png diff --git a/src/cocoa/AppIcon.iconset/icon_512x512@2x.png b/res/cocoa/AppIcon.iconset/icon_512x512@2x.png similarity index 100% rename from src/cocoa/AppIcon.iconset/icon_512x512@2x.png rename to res/cocoa/AppIcon.iconset/icon_512x512@2x.png diff --git a/src/cocoa/MainMenu.xib b/res/cocoa/MainMenu.xib similarity index 99% rename from src/cocoa/MainMenu.xib rename to res/cocoa/MainMenu.xib index b1951c4..38db0d9 100644 --- a/src/cocoa/MainMenu.xib +++ b/res/cocoa/MainMenu.xib @@ -7,7 +7,7 @@ - + diff --git a/src/cocoa/SaveFormatAccessory.xib b/res/cocoa/SaveFormatAccessory.xib similarity index 97% rename from src/cocoa/SaveFormatAccessory.xib rename to res/cocoa/SaveFormatAccessory.xib index 3962b6a..3baf4c6 100644 --- a/src/cocoa/SaveFormatAccessory.xib +++ b/res/cocoa/SaveFormatAccessory.xib @@ -7,6 +7,7 @@ + diff --git a/res/fonts/BitstreamVeraSans-Roman-builtin.ttf b/res/fonts/BitstreamVeraSans-Roman-builtin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..550fe3648afc587e9c687ced270a5ee3d300f186 GIT binary patch literal 54524 zcmdSCcYIXU_BXuC>C;>4B$@O=Nk|9~2xJTpsu1a*!~h`_=^X;nBmpU*g&H6tAVfq& z#DIt>k&fj8dbuFIcLtIdSe%T|(V6f)rbBl3p6+c~t>F6wgq&K3 zp1m_GYAYCg-v;NCy7x>>-v9i)Aqe4}@V(EF(SydFEPrJKLQ)b!bjpw^lRUNOL(&kk zl)^RD`QwI-zP0~fl?bW)`o+TrO&o`0c%h@?;Jb0ysOkBMAG{xpkYhhWFCQL0bWq-d zr2ItqZ9_QUY&aaSwpYf$_dNI>HGK4>X>#t;0E7s96&jBkJ7m!1kV88VvaN$-L8Avv z8z*)k=i&N=aDivcpwUCqrv2NC&}MjE*sO75Cr#w`ciAEqqIH!=!&gOb20sD6lSi`< zxX)oYzeww<--(~8`@;r)1RK%Q(ECi_1wmHi<&+Ry1S1M(v0%o_k%$DbgK!ohmg(D* zo~W4QD})#%N)>?+VGcrXT9K!7{k=0sOqvMy@;oGxlR5DQc{IKO=XhV>PdJD2`JOEH zZ8R09(mm*7X+PR5`lE7r0~#;2MsrCD`k02Hy|8T)5NZwQFTrm%lN%@x_Ls>%ppZW* zg6(VA*1%Q@TOMruKE<%@gl#cwbK%;%ux;k|sNaMPv~CF zJFx(@hGW&Re~$Nsw;A>)cpC&%TLt|p7jB^f`0XJfAB~6oc%cf7C!eE4{#mr6LnIA- zNYZLA3tN4kSM?1s!252meM9tyc9^o+1Ycc8r`&Rt3kb?S&{uq&c zSm1m9e4q>Bw1@p}P9xZIVC%uJfp$Fy+m{N$!SrM3=eIuEaoY1^aPA^($M}6{D$0Rv zuz>JH`28N(Hgo>K_eeOt2!5XnzyDDF5G|_P1_SMjb-Huf|7nB%DV!Ia507uahgPs{ z1zs@NOTZtko)4TCPi*^fAbii)dBOQnw{czwU&4Jgpcl~nuQvHI90y)-K0Lkw4^99t zmc#xUZ1;5f*YSaWetgsU`z`9odGYwhd7<-~@1c)6&+GTVM=o=mpSsKe--X$=Ka(s( zlwvedN!0mW#Q6-@==*xv^6?ji0Z%@x+fD%eH^ApRuG9_zSKmPbe?;#23pU5|O#+O`fU=QuXM%aJKH;|hQ*q^|l$>ERI zZEl|q!J~Bj1oi?6I2+DogSHVtf4zumv5ZaFhQn|ZoQZqlL3kQ|5w9dC$W^+F9`!gq zL7q@ggeS(6I9Uc)uBBTgQggwF)5fl*`(IBE@L~g{;=+h5LZ7tl{gUYakjc~_M zoP?jkJ#Y@(aTeV1IQhGNM~5fCKYsjrTI_5N4yeAVvyr`PUY`|;Yh z*KS_>=GygZSFU|_ZU41R*EU>kCOy)q(9Zu;e-hF7t+;9U(=9=H)E%kc|LKB9Uwyx% zU{gdSfkjb}iZo134Rr@;c?R`BJy9>z8}&hbQ9sll4M5ML=g>fugPupZXb|+J z5EY?CXfaxa)}hVlRkRgtLp#uR^cvcQUPt9a%dI#-C2T%oi7ad0L zq4&`d^gWt@^3YH;0{wudp;yp2^aA=9nu1268nh5?Ko8JF#8537hNhyQ(J$y8`U*Wn zqtHxr9qmMi&|EYGjX}SoFHkl5HyVv*p(->O%|i>&T67M5g1$!IpzqKvbOYT)2Z7`t zBJeWMU33TigfS-QpJ+Pz7TrcaqLH+=X0sSYV-?yZml&Nt&nC`ibe@gQWpoas zvl;c)zBp@^_#&gTX3?2>w4j$uEQqEv7@f}OG&7xQq*F9HnbAoP=)?zf!UH<~0UgKa zSVqS%Ix3vL!01R@rZ}=E9l_}ES#%hD$!ByZqj`)DVRSH~gIdzu2Q2QvCBqXQW2 z-%k_!KcM|I+Se~o?3+ycFxnfQ+&h!@a?zgHD)tPZJ)HEJ&JOVzMzamHJEPs6wu;>t zecDR9GTH^sbYZk}C#%@mK|6(*#7vW6Q+D_A3nZ$H&t%bHS(Uy#+G1{WJQ*7~oHg9GXn>%TdHA zX(&xG(c~ruF`3aO2AY&;5R*(a(LftFQpLtr+DN4hlWBv5Xt6;aO^COP3DGp(PUB*u z#ki+vY&4CDHi$768f~Caj7BmVVWHvB&u}~Sk$coAs?$990dIS*GBp)<4`{I||vmCwNS zmqdc@;a*@9OK}=@;xrioDWRLNU+5uJ2)7^tY%WX`&I!4~M4UpmihaZ#u%*$Di5;B+ z51;~H2kUr%T8_Sjr+`}s&nO0KTmWrx;<0EpDIh)I$O-Wr*wqba zESx`wH{-?x3|bG*8mzyE&_?1#{dE`3h9~zz+oTG~DM!LnIK_70W4KCMh04%*`WzikFXIJ5 zq_9)yh>Cp_pt%t7T*MprZ=`%Y9s15+1^n)0s*sDz(f2~GJQ(i%G4ByPc|Yj^J<11< zA|Fkatk9=ccmZ7mE#+rJ&^fuYkO;qrJIJ%3*9hXjRPZmx!udUDA8JI`K%`#a>#x*Y z{1tv#DtrxnF2+mAujm|Y2cA{Fa0h4&&KUef)V=zl)e!zykRG&<9-t3|7-5pISvVwo zCfpP?v8(v9WR@06RdPSMQqh!2$~A?lG3sFTCH1J5to>{lWvDb-joHRI#&RPwH8k}x ztv1J*JDX2h+?K(Xtyaal(q^`0*_PRu{feX9X>?9-?r~ml39cAdx+~B1z%R>hrQbFG zj{aoQh z+YRqELXC`#0vgS19MCwbaZ=;-#vL2?YP>G7VPcEKc8S@E&m|5|oRrifDJN-U(v+lm zNz0NpB<)Drmvp4bnkHMD>}hhS$?+zCYjQ2wn0!3>Z^_q^|C#)23Q6gal9MtrWlGAt zlw~PZDfgQSO|4BEHyzb`2Q>doFEU+Oo9WX(!WuZ5h?FN6Qym9&PzmE3MUM=}LNLYj^9xtuM5Z+QhY) z)aKx)G+&rY6j{U+MzvZ&sg8!d>X|cVoLgOti5F|evyFHtJmzCs zPQKTlC8MXk77@QBpt*`5N=PJvVy&*e!rOcg9?2hN@6~0mqt~`hhAB#I{K2WTXv`Ehkas@W_j#0pIutA`mMe$zMDIeGEgn-X_5YR3r-_ zsu*Y@Mg9(p%N7>u4i577b2%M$o7HSGs)}6aX$NTmlE+$o)}PZL{X%+rbvl2qOs+~! zPV(9e*gy1=?S-S;4aZR< z%7)<oQo7O_Ei3mde7?OV?V%CT&_Zyinm8dt&6Z}f`@@!&1W|%)6@HYh<{xeTX^(cokNqQnohp}{B%PB< zD5;K-iE?WRtHO8^2q1fNn7^gOE);u-E1z%in&=!ha zPD!RVGwl8RZEfIu4B4NDNBnJM{^w(_p8Chxyn%aq_I&fX8(-Y`B5%@+@n6rLTfola zMx;@r{obcAJ{h%p&8vsaKin3=g5OMNEU?~@`%8BnvjEh(!8Ed8pWKVAyKT&Xe#5yO ziD-Q74LM%C3vshQ#KN&)SxdalqAbxd(J_|TsMt)j(ipn3@zQ{mQPN7|(ir=axTqCP zW5a`^RcdmXRg)#$)W95UvNSO?wN_VF-K(~@=4h*X!KOU9B?=;sG0H^qe^p&_VHMWLY~o-h&~YSSDV3{4u~ zv^g|cA(CoyI5dI@MZQxczy)XEtV7GR8&oPGEtJN&%u-yeR1(mjBzASwihw0%ht}9& zR#9WSE7lxJ8{1>U%{B{+Onb1kiM1MJ`a+fUxQ`nk(_F0ikUxI&*Y7~it;e~fgP8l* zi8=5_^hx;>yC=R_mH!wE{K%uD{Ui2Sk})3W-6tEw2sPe~+&GN*8xl|gjITms^@oJI%Xcb;dO2ksRRN10zP&XPj7?&rPCGSm68;AyCeb|ANHCJ&P z+&o1hGLDQjs|D5C8mZ?+azm@94w|aCNIp>5y^wKb51*tKpZ>T<{x-AOL5%NvV3$pa}Ya0spt&pqRX3 zwwNo96PJr+qNJM>FeN@a!sQsFz3gXFAlU&g>+ZEk>(E-WNk-HzA%|wRUb&fE1u_Fq zs@9+6;JVJNKg5j8^`nxjIDTB$aB#d{!%4*DwEIWKkkqF3=A@u--uwk+Yf4tHmF(ZK zHn(oER^R@BkAHO?S5`qgwn00_>g@;vYt7psV`Q)k4h2Cw((nDz4o8Zg-Q^^5WHWne zQ?dy^5t@|BAKz;amLity}mby??DqXL=0AsSS1WHnf@yNHD6h0A`u4 z7j4G%8aA)6D@Ki~l8s_YkkcXt3U;4`cUPd_vG*hfRo;OVT0 zM=lspFeF|U1<<>ZUK>(zsZ2%U5|F?n;pg6>&e5{$!szotgr0=DkTC&g7pf&o;zCb;5-ss^DUXbjko(UyzPN!z_st9 z`GnuzCycRzLtlQW} zh5>XqQOaxXgQWh>la1i(6xH4kitBBZ*CCbJQJHateMx}owuDl*D>wi;c#jW?o80~+ z;Rs^0+EbFj@gcFv$Yw<_e&L-gF5R>V{%zXy5UcFBhY#6rSQWF`Id&Gdb2tTFO>s&Y zo5%`T5u1pY;^}w>UdsFZHDnYAKz}sU#p}wXWdbP^=gO!|Rl+1UIAPcT)T-noGqEc9 z9(PfpTe_2=4-kVsKn%ccp9`?xLM;Lr*gV`Orbef5?lRmNL}=(~+_`#NxiGO~MaKsh z%X$AXM^S~&(0@1D?u`u$3Z?;W8;Fig6opLdE4J09GUo~bB4=a;8=|@WtyBsDV|=0N z=uzDc>n(bW(7Hy~+vWmC#jno%!Y~fehj$1Hsn;RNy@ftvA9;o_L!1&^6expw2^4|= zd38@hQ>4j36N4wY=b^&Dc|r4n=ec*HoxwKHqS4TuRDjHN=LPhnoZ1GbBn#ZJkr2f6 zA69n-+NKP8`nAI6&rh3iq2F!nZ2xQ^yH{RbJ{7NMIeKlUscSOZo^6tR`=jT!j|*Wx z_~=jyaxf9-5Qhr9jgiZt6{=x{9!HtWRHm+y+-06skt?Jnu2L&PoR^1BvJU*^V{eT$Yh5#MnbvneO_C26^(r1+Wp^ zMH9j!Vz>wAb4VKCRNsduI!iNFyvjafw{uR6>~(VVkrM~E?>SJi`PFqjk4%_&s{eP` zxQs@JRW85!b9D5vCdq4xUnhTM`O7(rKFNCzFI8~K}DVLftSPl4BUO6Rx%I_dMU{;bQr2?of2 zbc8I$2*?$@jATFD0L9=2v>EP(yBoWk`rv##8PA{#unCx~LQs%m`pn@(v%=d{hIk7`W#k&rh-{RA*Hv3jLEsg6}YL;CS7D<9ZCd>6B#T zg5ctD=5d}~rGYhN4f{2GfTP$2_EXNW;RBAo@W$!WZ#?r#FY#hITWPVdJOBI_`^Doq z(+>*n8VjGuM;pae2$!>=Y%uj83`z|FO&>~`0Ftz7wf(Uq4j-< z^l;S{p6o}qE!?tYA#7Ofy0PoY^OjcoUicb|?Cv+LhTXy0IJoOZ+Un5OSKfQ?m92-! z^opn$_A~pb??Cp`58ttW>dI~~-X7}X*G`bT;lM8m4eO?R>Cu{R z&SO@SB3|tCV6Ldgx)d!28ZXvq90@UDTW@p#P&ig93vEy;-_k&Y9wTTp_v)g+tK<4fAKQ@;QQ+-4>cb5X5VCmr z_U+4;@7T_^&s%|NuU%&==B|8|{r1~$?6++lSInQcYSp~?E6B$iii$RDEG*j4$FpzF z+n;^*_MCm5h~veVZr{FCd>jv&G;iJ{*mSvp7(yt5J_SJjv7a|m4hzJED6mZ1E|j4~ zeqm+S6@E*iWw$%r5sD%r+$LRYU?9|6x$oF7^-|+k8Te7q(cq)*qah!KR))#t_QUq? z?G)suxvrt?jyeYxrTAneA_nVm6m$$?s;|42b^&Q=xo^~0>>;*(1M!NDy~V!mT8i7$ zNmLlf3iv$sKF?vx58q)w-9T<(&xVq<_435W0@N1aW00@s*p1t3mgWmPz(vqumjHl5 zKp}DK2%^Fr1+F^oo`*ligh;5gg5T@@i~f5RMSC6Kk;t2c`Dh1(NFojtP=F7+(OsFF z)!foMd^gfPn)BSJfm_vFs`qOyL8ZxMXoCdEOadvT0>!0JZp3#4i;{>+B{5SJAo(cL zoziPEq%)(0NRVp4hhTKxtE!8Pc>G@<`bN{fu?cKc% zq*4T~92HBU?SOT|2r3mNLU5)eg4-$zJMn9>M55sS(D?MKCmOG}ocF-ji~bRgRLq;N zKU0wC-2*If2TMGFxwAze4~^(vkV^`3InLW4DpZvOEyN)pXQ5-EwZwk~a5N;yq>6$T zicRhy!43RHF+q-K>lJW+AmHSqjYpOo0Z6>_E4z2cdJYb8x0%QKpcJBYr4Wsx>y*NQ z>FNw^hR27sIU-@~WkB==f&L?N%EzV!f-@Q7GhB4$%*P+J7%(6$W&Wt{Zx71(a9G9l zjsyB7#wwD;7+$eq=)6AtQ*)a1ACuMga9WFxwsc+8r%z&Pper4U-FP31SgAZ-+zQTE zS3oA+A%y z)=yinRDt+`T1`K$Yi>^P3($Kj`|-kY>&JJJ!EiyBlMsi9>HrQM4+8A3-p6=aH&9PQ zv4GDb^s1I_VcinDWpooj;XkH(pxZvEg9o0eDLzOjHYSP_#}{V#2|Qx;mjqB;vA&S2 z87MTtAGDbjLdFNnW)>`6K4W?@2~XcRZ13-R#Wsy<$VR}&(PVIPC4Nw~QBC0|!0VAqxX&YU zG*d4dP(=+q?@Aw*dGkV5z00G6860Z@Ov5*NeGvx#3?PNa$U%yh7O2XY0yPs$kSJro zVW=wj;Xu*{_aoWHVR$&1hNqC3bb>Hdo~9Jxg=CI#9a&G;2>w0~2m*#k3P>n9%0@0`kY*=IswYpB8`gAsW1ZKa5y7 zUWMDex@ps^>@aSydc}&>%s_-&59Z7)+0O1hthr52)m$xFykr5%XKlt#7(Z^u(YF?D zb$ZUMJNXaLKNA7tiUmCoh?;qWOs|;tXeBoM3Y2bv3a~8+k^@aB$!QJZO{_CXylubU zN!o7-4htsGJnpB|`6kVpyFiHR9ThP)|JJ-(#O`7%M)PjvkNoi^_6D1Q3vtiFAH~5J zpU+_@*x%VDb|UBb^Bp_lEf_d~w{+0QF3?fjTSv#nUKc7+fr3_rSTzLLcQSITf*l&A zV+KB=fxX$6>(B{SXaAzZ^`^vQyy_lk!Z7wVJHs;JIs5P$Hk@U%L1N;=sW<>P#tpIm z4z`xf0icH0vS6K$i{beO1W%Vr1X2QUe~IvhCSp|v-&HVjl3b{)e8jJ$ePJeey6%8- zClt0*bZ<=%IZ=~F?pL?r{$*Bq%?+R4=W-GW_g7K8*Isvj;SDez`pv-r@|*k2DA>P! z7Q^GWjyy#N)Qlt9HG5C-_DnSEErwzA21 zG0s_u<+0s7KTN%Vne#o`u#&>ZlP zQ>Wq954dBBYuB)R{hkK!FB}GfYroOpJRkdwK~;r zN-}v(*`{1mv1yA*e~wkEiz!c?K6m5kjKVSS&{gc`d*y3Bf=AcUY9#bWLqB=r6dQOm zvP}YafII2BCe2j9F`?*9RRYIDQh0+5KIzfp!*q^i{gK|NSB~7h*{4ttD(^}s!2(eg zKN2U#Da}bUv8mF5WQlE+-eee=LZ*uI$wIMMSxq)7w}=aLr>IK7G*A{nQ_BG~PHZ40 z$jyXiVlydKPBK14y+V7@D|uzFaWKt==wg^WRUBtrOc#sGq++?)xPfkxHpvI*JMzc$ zWBE(^g?yWSFWeUYDg8$8i@!?^2aZPr#{(B|I7FY29|LZ0#zIXn4Pw96q;Q@uB2#NR zR^K3>)--uUCxO#R1dr0l2djYHv?UTaCdR>IjbZ$Gj8<_b{RlyiKW1R%BwsMBzJ1R80k=rKy>kOsE0`R}5gCpmapkB)8B;1G<~| znBrQT3oZ}P>nUGvnz(bxvm*P(nK|YAP}!nbRHC_vQ+8-EBu4hgF`7qfDyM2A$V^fo zPuJ#J_#K(h zS?H=LVVSsvW-|1W>n2^!c`tc}^$+RNr7Dc8DsNIV(n9V?I>;kPzC4G>5>_P_4wABP zC#f&)Ck@3Tr0LQE{IXO6RSa7UR=w32#<$LJY$a37Eh#k6W#t&54xHCxggk5pH~`*beQHd9r?FR94W?JFV9aW- z;FfqATgZ;Hk2!`bc4ZapTlO8Rz#VZA4#pj~v1i$429Xfnh6lrIJJ5{iHexBzOo8gt zmfircPuxt|t(pQ^Y_L)pHg2Yz&>&D^KC#ddL$AT&3Ug(+o_D?J61jn|1C_w819&Dh z0G{DhZ0Uv#OIZti@*!{GLv~tBtod|hQQ^uRH!ffOre-In1-oBIixAYqn*b=N1shF9 zGd7uwnU+waPMiP$P)(txU<-J^fx$Y7bluN271t2fN}W(?k2zEzqyA}tBI#hNk8}=S zdgKoRk|zk5fwg;A!y~Oe`axj{(DU>EB;$im+0~yQp4kqC)Ida?sMTy)9W6zGV1SZt z^p-aj{64AyTo1vJ4T7OeDEM(v!BwIR2c(l-Eh?w2zzY2xJlo58XAAdm_v{f{>C&eP+MK)e zLFdKOuAIgvF{++lvxu!MDOpJl`z?Qw4ac+B46a!uUi{+kOAnFmHFt{U&tJfGKgYwj zf~?1)7rp5wBQYCDXjrJC5?Lc*VWF9tAv8>IVdQ$nxjLZ4CX}Gn(crYjg=&VdU>QXO z2bvqnfzF6H2-#pv+=Ng~rwds8x@_(7M=I*^yf+ZX0#4Jw17Y!r@!jJAVR)1W3;PRH zG!gtKAF?8JoOt&6?Qc)rG2@#r*;RIHa`xFC+dTZu_`-vpG|S z3=IoxaOuFMuaXi!YoE37#W6F(0va8C{rF8l+i>jz@Qrw_g^W4_y5LjhUhuiRqGG)O zNE#@fd%ijgjhplTD6w!LH4Irng@nmgD7F}t?4vzr*mY1X-?HnVhbIFva+fZyevVyXU*H5BrR(DQ zxJg3qdSeA{^P$R-`m8>mNd;C3qfE&8yoq>lghcQf>tiEO$bTeS>cI@%=42~e-UlO^ zKsyK>!~t{`olgO%mx&^9-RvYmLXemMP;d;17vjZe$)mJDkSihSLb}*o>WJFo_N0^0 zN$eo?M}4GxGC~+3&OlQjbeb+q7bi<|6v*jG@h~33af%8t6X{%Y{5-yd|M9n)6QIZa zh3~mKnUcJpaMkV<`>4kSZ990i^4XTQkSWL zyCZ>3GAKrarYgQ#7lVvU)(dr&E>Pa`*M|2|eCV^sAm&K7!#_(z5@DtSyzE+>7zGx% z4QV4b)snQXOv}{@AcZzloGs4L) zSw*U#X+~rYqELjU1RAYoj}R_;B#-P-BGo7@+TbyJ%;}^hO%+naB&C^}X2>umnX^z9 z?o4$5Nz4S}o2hseui8#~+UPZV&HV`2dtRB>dxv!Jrm zZt%BrFvAb@_(xFv^w2Kc;T!l0)(3$40q2%M{{EaBxhkmJzn zNk9+$hr3+?q780spyF?cRbmZINeiW!p^MT_d{)UPFDO&VR6`+|LyE-3$^ye`vYf0H zHz{ij!QiV}{ig5Ntr8X=ETM`_b2=g2f=mO5LTW|)KKVDPBv0`VnfzMGBOZWUavms|+@XVd2f-_XMwL}vqv{yme^(tyknujo~tA}vC!UB&3=fl6V-Nl?V@Vs*2E?Fey!?jJk zD&CLY7HB&RWe~g@z==8}7Yfv%6Mx1Dcq{N&9JHPtlJeQhcnbL71>mDKg1T!B(C)*K zwGT7+t7f#zUpi>Ec?!c0xerEG*p~Pkkv|PEsfrx9Kc;5UdZSH|yLyrvj_g#{bUod#$rTy_; zYvz{c5BhNH`$z4w7dLKP9vcg)iJ(J3t8FkI49JaIcmwG{qeVR!;96p-2woeA?Dh@; zMo9_E(*1bwv-r@y$uoP$?<5__4V@EO7D^#n($fXL8bQb&=-~+g;5?R(2Kw!5D_6e8 zx0+=w-zqqZYHQCHyw&pHL6Ugp)~z$Jksf)2*kN`bUWW(e?S$4~UK>eo0S^OFhBp`$ z;)Q~_(6mrHXcG?l1M`Apy9sr4wg(UtYNbF18l8ayX>d2m@U59J;? z${eZZV5lBL0bWaQb=#Ev_VMFyXK(8E^!9-@;O;fTQtz!o>h1;&Zk#)Jqd|l6s3_== z8QXEoNRU;++a(Nu7FvDu3Ob0)&V!<|#9V>bLWqJC(!pjowCD5!f0)B%K6+I?K`#!O z>Fk7J83=^^9?v<@trZn5-Gj9-XhZT6W8^m07 z1gvY8dYa!GpAj-Hq%7pKkh>u;DLw;dkPKHwu-H&eR1(#O+E_Fek0oPWV}sR!<2i}L z^>U0qpk@#O88B!_lL)h`_ZiQBpG;O*k zp&@R8H9#xdva6MA_wRucYYh2jMD7Ev9nd4UXvIclmxPO;L`^!Z5eFo;RZ%fn3|%3b zPp^c3l@Ln-!8tr>y#oapdO)CuBTilQIe|KLRlwXo0c`@`;W==;K2Y$wvR!2^9$i7( zL%2Nw+ZdUe0(r3%a)0lTr*R_t{NUcbd)}9v>$8UsDXvbWpBHy~4+=&=Hdr5F0Pw&7 zRWh9+%U}#q?S&4%gBE%)CbA;-uzJw)eo#nEAX1DSB)i?yo|nedi%I28pP;adoC9ev zx)SCjlqLLWbopD!9|aVx_4Jgl-ioKE=+^BetG91oU9!D`u?K^8Kl9Ay9`Ec=+c)!* z>grEs?n|p6txsOLa`MELD?hMr*!Ll!Z#PVM|AS|T3SS`DB#)Q#)REFD786+${s_48Ui!1`U_l<;udrx4%+&zXUM3waeY zXO`?fcrf$r$sZmk+iIR8o40H}vaO~_a@K4fn)ef@--qz<>F`{h!)^dk<`Lm7bQp5j zih!~n<*)(Ig%QqUNMFG|jBt((^OW|774RqIJ}i@*-^2ZCFS9=S{R}W8xt-T-ATlx^ zG0H{a`{=OoEtsMrO5IIZF`_K1o*};pY93Sx@PTJiITPSPHm@z)mTMbl^F7E}4-Wet z`06`ZO-6LppSt9Wqosq^OL6e%IvSQjUo@~l@&6qoK8)7ZgTxBTdJ-hQSsyX+?C}#I zu~pBYJueUqT>L>;20(@5Xsp0-;Rb*K z52v}tMRWl~AWH%L-9XpM<@7c2fc&xih5Q@6OMesY3PBw4<$y1^h#ay!bdW^*2<0b+P@Wx1XHNb+T5ZPrc3h~g2;KPAd8QCm~B4yhYOSU@z%uyuhuH@=!K=5=g zIX(U6JssTTLg;lQMP)~gi&`F42ComIu1D2IsUVfQ9J(Ht%|C?HVFzFbq)HjqXr($2aV4jP1bdeKj<8XbkL3t8Y_5x zrrq)kwY%E?sXo&inGslk3Z&WcY-KjYKC=x4#@VLX=Gm6n*4efKds*P!KpV8?u_B!( z$~Y&k-u-&Xs@=O+-Nklx=kCAQPuNDU-#T^b*6ougzAt4bSrz*clzbZKawl$~+ss3t z7q>whxy@|j4X(GD73L-QeR?}3btaz5r|K;fD0W`NoBsy;vCt58T0@XFs5oj3p< za~IhJ#NVBwKF&Hym%lIC)V3?GVu=!-?F0OktbBM=P~o1SN{92^}2+1 z2Kw1hlGiB#u401;)#eKIu&hZCm}CJG2AK_R`R>8IgR?LulJ;i<-o>R#H^N-%eN^-F zr=7z(H7tD%D0OIoqp_Rrx7*Gfso4htl|Muj_2-QR-|GZCFBaXZ!#I1^L7Y7S>#Tz~ z!6#cJI162i0=Q2W4L(^t#@QoSG0TcGqJ121*M%oAPH@tocIuZqe4y&_p%3HK-#PXZ z%Y)j{Zq03Q8ykb8(a_M)*h+=jZH87xg9mwV6p7Q~3<-`zXQC^?FD^7LEZ!3z9u-@t z6&ebSg(f@i0U?qm8K{w(sF_-*l?Kuv8cf|VNh>xnJ|q75_}TGu;+Myl#ovt&fY5RL zA3mZFOyv_?{wOi^fvL@ajdXFhodXsv8oWBAa{F(8A8>3`{_#QcmJEH}`}(@CKFQxN zWbBEH>($HKDcqc}eo^Ux$jBq9sr{enk{xY{Dw(%=cU^rSB!VU{vQ?Blr8GEVawl0ZZU2HgaJLh2mIH5LhhgS)DjIJHgdb(4Gg?)+rfL) zeW^d|DF>MJM;K~dOtOa?_ctmk+P*dU!;|05%aa_z@+Jb>lTsqWSzKLJ4_CX zpO6#uQ!zv&Ff&Uqh?-(hVP|xaK=*gjjDAW#MKs)d@vW>njye{sNcFC_R+l+6T-yv_&_XSubME$^A zNsiI8!fA23E-Jw|1^(ek1CH!d@!hwVzWcU~&k&}_leDG6JZ-(OMOZ7Z)Rt>I@Gjv^VY~dQR;IZD!V)2!VF2J? zbg4lG4jM=-bvQC?eO=S-CCz(3YEU~@X+2A!nDh2l^{b^sZkJMl8qx4bx8?sGf zP1Epf(?+~nejRU<_nJO6T{qR55`ify;b@o+-3xi_1zdiK9b$(r;kVg@OE@0K3%NDd zYd*vktRv}6{MdNx!vtG{-@$dF1#k7Xg{g?D4Ouw(VJSnHONcBco6!WHO*XR$2D;G( zh<>KYpjweZETZq54Tt&MVoin2i((Nh1}iYe#}_{N6H;P2Z1o6*{Uho0pEcaaJ-%ucVULLBEc+FKh57{HAR|IO`WvvS~t@Gb$~Wf zD>BV7tuommbptsC(P%ImEdJOTXWF50BQ?KVhgRAp_#FTDb1W_NwPhKyx2>;P|V`38?u>dd#!`1lev?{YwLxc z!OxK1G+W39zV!ya^;P@Auc0t!*gqSTsW-nNKyzz

?tm?LU=BiO$ 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 literal 0 HcmV?d00001 diff --git a/src/fonts/private/0-check-false.png b/res/fonts/private/0-check-false.png similarity index 100% rename from src/fonts/private/0-check-false.png rename to res/fonts/private/0-check-false.png diff --git a/src/fonts/private/1-check-true.png b/res/fonts/private/1-check-true.png similarity index 100% rename from src/fonts/private/1-check-true.png rename to res/fonts/private/1-check-true.png diff --git a/src/fonts/private/2-radio-false.png b/res/fonts/private/2-radio-false.png similarity index 100% rename from src/fonts/private/2-radio-false.png rename to res/fonts/private/2-radio-false.png diff --git a/src/fonts/private/3-radio-true.png b/res/fonts/private/3-radio-true.png similarity index 100% rename from src/fonts/private/3-radio-true.png rename to res/fonts/private/3-radio-true.png diff --git a/src/fonts/private/4-stipple-dot.png b/res/fonts/private/4-stipple-dot.png similarity index 100% rename from src/fonts/private/4-stipple-dot.png rename to res/fonts/private/4-stipple-dot.png diff --git a/src/fonts/private/5-stipple-dash-long.png b/res/fonts/private/5-stipple-dash-long.png similarity index 100% rename from src/fonts/private/5-stipple-dash-long.png rename to res/fonts/private/5-stipple-dash-long.png diff --git a/src/fonts/private/6-stipple-dash.png b/res/fonts/private/6-stipple-dash.png similarity index 100% rename from src/fonts/private/6-stipple-dash.png rename to res/fonts/private/6-stipple-dash.png diff --git a/src/fonts/private/7-stipple-zigzag.png b/res/fonts/private/7-stipple-zigzag.png similarity index 100% rename from src/fonts/private/7-stipple-zigzag.png rename to res/fonts/private/7-stipple-zigzag.png diff --git a/src/fonts/unicode.lff.gz b/res/fonts/unicode.lff.gz similarity index 100% rename from src/fonts/unicode.lff.gz rename to res/fonts/unicode.lff.gz diff --git a/res/fonts/unifont.hex.gz b/res/fonts/unifont.hex.gz new file mode 100644 index 0000000000000000000000000000000000000000..db7a7100acba59468229ed1e12d84ab483b61900 GIT binary patch literal 959159 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%0A%pQjOJ#4f|sHA)Pxfh%Y$}hr`oc9|_-)OA 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�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

K6*BaJquGs{r`7D8_9wZR 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μF1@A(WDCDr?&lL3 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

0P&2I?yVDA=$Sc}f3aU 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 E0NHcR!~g&Q literal 0 HcmV?d00001 diff --git a/src/unix/solvespace-16x16.png b/res/freedesktop/solvespace-16x16.png similarity index 100% rename from src/unix/solvespace-16x16.png rename to res/freedesktop/solvespace-16x16.png diff --git a/src/unix/solvespace-16x16.xpm b/res/freedesktop/solvespace-16x16.xpm similarity index 100% rename from src/unix/solvespace-16x16.xpm rename to res/freedesktop/solvespace-16x16.xpm diff --git a/src/unix/solvespace-24x24.png b/res/freedesktop/solvespace-24x24.png similarity index 100% rename from src/unix/solvespace-24x24.png rename to res/freedesktop/solvespace-24x24.png diff --git a/src/unix/solvespace-24x24.xpm b/res/freedesktop/solvespace-24x24.xpm similarity index 100% rename from src/unix/solvespace-24x24.xpm rename to res/freedesktop/solvespace-24x24.xpm diff --git a/src/unix/solvespace-32x32.png b/res/freedesktop/solvespace-32x32.png similarity index 100% rename from src/unix/solvespace-32x32.png rename to res/freedesktop/solvespace-32x32.png diff --git a/src/unix/solvespace-32x32.xpm b/res/freedesktop/solvespace-32x32.xpm similarity index 100% rename from src/unix/solvespace-32x32.xpm rename to res/freedesktop/solvespace-32x32.xpm diff --git a/src/unix/solvespace-48x48.png b/res/freedesktop/solvespace-48x48.png similarity index 100% rename from src/unix/solvespace-48x48.png rename to res/freedesktop/solvespace-48x48.png diff --git a/src/unix/solvespace-48x48.xpm b/res/freedesktop/solvespace-48x48.xpm similarity index 100% rename from src/unix/solvespace-48x48.xpm rename to res/freedesktop/solvespace-48x48.xpm diff --git a/res/freedesktop/solvespace-flatpak-mime.xml b/res/freedesktop/solvespace-flatpak-mime.xml new file mode 100644 index 0000000..760799d --- /dev/null +++ b/res/freedesktop/solvespace-flatpak-mime.xml @@ -0,0 +1,8 @@ + + + + SolveSpace model + + + + diff --git a/res/freedesktop/solvespace-flatpak.desktop.in b/res/freedesktop/solvespace-flatpak.desktop.in new file mode 100644 index 0000000..b16ccf4 --- /dev/null +++ b/res/freedesktop/solvespace-flatpak.desktop.in @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=1.0 +Name=SolveSpace +Comment=A parametric 2d/3d CAD +Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace +MimeType=application/x-solvespace +Icon=com.solvespace.SolveSpace +Type=Application +Categories=Graphics +Keywords=parametric;cad;2d;3d; diff --git a/res/freedesktop/solvespace-mime.xml b/res/freedesktop/solvespace-mime.xml new file mode 100644 index 0000000..8ee132c --- /dev/null +++ b/res/freedesktop/solvespace-mime.xml @@ -0,0 +1,8 @@ + + + + SolveSpace model + + + + diff --git a/res/freedesktop/solvespace-scalable.svg b/res/freedesktop/solvespace-scalable.svg new file mode 100644 index 0000000..0d060d1 --- /dev/null +++ b/res/freedesktop/solvespace-scalable.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/res/freedesktop/solvespace-snap.desktop b/res/freedesktop/solvespace-snap.desktop new file mode 100644 index 0000000..8441258 --- /dev/null +++ b/res/freedesktop/solvespace-snap.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=1.0 +Name=SolveSpace +Comment=A parametric 2d/3d CAD +Exec=solvespace +MimeType=application/x-solvespace +Icon=${SNAP}/meta/icons/hicolor/scalable/apps/snap.solvespace.svg +Type=Application +Categories=Graphics +Keywords=parametric;cad;2d;3d; diff --git a/src/unix/solvespace.desktop b/res/freedesktop/solvespace.desktop.in similarity index 66% rename from src/unix/solvespace.desktop rename to res/freedesktop/solvespace.desktop.in index d2d7398..8c6fb24 100644 --- a/src/unix/solvespace.desktop +++ b/res/freedesktop/solvespace.desktop.in @@ -2,7 +2,8 @@ Version=1.0 Name=SolveSpace Comment=A parametric 2d/3d CAD -Exec=/usr/bin/solvespace +Exec=${CMAKE_INSTALL_FULL_BINDIR}/solvespace +MimeType=application/x-solvespace Icon=solvespace Type=Application Categories=Graphics diff --git a/res/icons/graphics-window/angle.png b/res/icons/graphics-window/angle.png new file mode 100644 index 0000000000000000000000000000000000000000..f70252a937e1bac714ac402019680cd90484b7b6 GIT binary patch literal 819 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpv3@#yNKc?$D7 zr8{%Qay|JyC0HdSB_$8;IcUXW6)G57B3?2_e$H~mE7O|k9pa~Nuq>H8>*EpV&le12FT}w+#TwI(1m%&P<;y!eJAHk9_4W1n`1t0}pWo2XU|?V%ARuu3 z(ma1)P%xGR`2{mLJiCzw;v{*yyD)UH%6b4foCO|{#S9GGLLkg|>2BR0pdfpRr>`sf z0~RqJX^t;bm*xV64tTmahG?8mPB_2}433i%6POqU`Z15?5XQ#h^@Z!mnH%t>5CaueRCpRzdp54Bvx_^u(K8j1ENXt0Y z^gL!@*nW*aFXL6=YoK4mN?apKoQqNuOEUBG6hbm{QyB~m^bJk)4J`83UDO7uPz9+- zNi0dVN(L!QWiT=@G}ARO(KRv(F*LF=FtReX)HX1%GB5}@TWyJ=AvZrIGp!P;!BE%G zBE-Pd%E;Wx&;X*raC^lhpaw;d4Z-3T~N2spa`a*~JPb WdIqo5)JuVi89ZJ6T-G@yGywo*dLy&| literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/arc.png b/res/icons/graphics-window/arc.png new file mode 100644 index 0000000000000000000000000000000000000000..807b7366dfa57d8af5a994b28430fb41a7f74360 GIT binary patch literal 686 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpv1HQpAgso|Nk2p81(n| z@87@w-@ktf3JUx7?fd-s^Ww#egM)*Gg@r{#L}FuOH*enj_3PL8`1ohfo=u-VU0hsz z_Uzd&Uc5+1Nci&QOGrpaTwL6zPoLhrc~ekOFlEY=)vH(Q>+8$P%F4^jzkdC?x3_ow z`t^JF?v0F$Oi4-k^XCu48HTX1uoWv-=;`UbeED+u^5xmt+3(-KPft(({rfjx%-e3D z3mHp-{DK)Ap4~_Tagw~{XE)7O>#0gD)q zG{={zOLKujOFUg1Lp08}UbrdLWWd9E;dGd&i-_Y!t%S8f;xYgJKd)KIqnmanc-G~e zlQ};um%0AA_I|N!vCM{x6+%k0jv0%6d3Lg9;w!V*G*9#M;jizlPnuj`T#@)BZ>L|f zb@oJCW`-$|sgAW8j1DsFyN@$5JjrB8=Vb6*GT%${YQ9u}fRKVdONVyh_1m_pEn8Wx z{BiX7R3jMlVcV-Rqsupbn=`1gDjZzL@BE&h>)_+zl?+mr%3x$*Xr^mmqHAOnVrXP#U}R-%scm3j zWnd6+w%QU!LvDUbW?CgwgQ2dWMTmi^m65rXp#emL;r5D2Kn;o@8-nxGO3D+9QW<;` qlZ*0mQ&Tb%72Gn5Qp@v;vWpc=^bB69sh0v3GkCiCxvX!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8Uop6XN;3=**-JcqUD+S7 zi1A2se3`m57bxWB>Eal|aXmS~fpN`32Y~|zwsCQ&Jy3gGRgj95#FA92WRS8{1|tJQGhG7{T_d9qLnA8# zBP(M|Z36=<1A~CG)s`q4a`RI%(<-4F40R1HLJUl;jLfYJ4Iml}w^vL8YET5(5S*V@ xQl40p%HW%rT$G=inv$8Q;FejGTAp8&U94cDXYfi*y%eaJ!PC{xWt~$(6954MfK>nh literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/bezier.png b/res/icons/graphics-window/bezier.png new file mode 100644 index 0000000000000000000000000000000000000000..9d2c240a471ac4f5277ad1c66291734fdc207a8b GIT binary patch literal 710 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpv1`lpAgso|Nk?bVNg&| z(ACwgtgPI$Y18}n@9*EgZ)0QA-Q9id+O?A>PuADhhlYj<2ng8Q+pk@_cInckxw*OW z^71V$Ez6cI3kV1Z4-enDbElAy(1ZySe0+R*dU_HQ6D=$(wr$%cCMFge8~fzR6JK9n zBO{~h*RNMsSI?h6f60<15)u+TJUlfuHOrSTS5Z-kiHX_0d-vwen@vniPMT4)YM!mEo6b7 zVk`;r3ubV5b|VeMN%D4gVd!9$^#F1>3p^r=85p>QK$!8;-MT+OLG}_)Usv`AEMh#; z9ABm`%>@c|db&7-hQk8yXs3zI=J)$dQ7A0wpCSV`Jm0s;cwn&%b%|roO&j zK|$f{*|QffUNkW=v9Yl^bLPysbLU>YdNpUx9ARPM*|TR)o;-Qs!iA?!oe~idxq0(u zU0t1ngM+81=ZhCF^78UZN=jNlqyzJ2TDodF+}5hYtT)xg9ai#&1PjQBa)VDWZmz+_4B{~ zr4aASGRJFolp9T+J+qzR!*Ue~E_bC(Osaiu7b9#YvM5YFmvB`*)l={NL#K&p_BAVG zKCTVlxLP6T@!wZp{?59U!8U1&i8_BCyK>pC9M&L1n~QIkeQ{Z6zhSmviNEshm>bjH znz1cvP_u8*6z-X+aptM~Gww9L;zb)$3I*d>f0lA?TzHZ9{NlB*d?vrKsHX zgZqswsw^8_dnUJ^_s*)-pPBWhXs_q@bB{YsC!4o)ov~}~J21bB@e!BM#*Nz?-GP1- zD{+k|aV|jD!649rIzOxWfv=$=o!3HQ!fQ7X7F_Nb6Mw< G&;$S&`#8t| literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/construction.png b/res/icons/graphics-window/construction.png new file mode 100644 index 0000000000000000000000000000000000000000..35ddcb274ff9113bfaca088234a5e6c7ac76b1d5 GIT binary patch literal 739 zcmY+BT}abm6vls%VRPw)lud20qSc09|NpkkjR?)nTsoO<(X5NMbk@qXG5@A3FqQQs zVi^>M85Ct?CVfDOP#IX*7k%7h7eQAM(N%P#751*W>EgVHbKd89-simMgu7;QdRk5z zknXIs*NLo2#^QzIe0S~TGm%num$}N|)=Blxz#>uW8Y=5taBvNDCSXpqIzL0J1Yi7M z8-V4}^Aoij#KnDgwbwB}KaU9xixPz51L%Nz5C=gjgf@5tBajO@Fbvrs^e5o}904=D z0HOFCmVq9sgabGY(~u8O;VKy6228;**a)T23lXru9vFh-uuWKmW;g}iQ1fSEyYLT3 z#Uvm;VGCS_N~pk5+Wt!nvOuY;@lg6{rYTwMs@iPl_#&-9po&)3w}3h`84KQi==dp` zsfyF(Nd1_SwIp}(_cK?*qIKJ8FY_8+HTZRC&JEQ2&S~VS_cPX#?D6sV!0X5H*-6Vo za!0IRyWM9`Nj;(FRncKXPgawxTUl6`v0Crz*RQ_vG4pNybmnMpRF%C{+k87ZyrM_j z`%T(rG+dhNcRF*b^RC~#_`|)`HMFzb)1Pi0o$=}G-)mF}!&H~1)1w|t-0d5?u<>Z3 z>tg?{6UK?{<`#8TUWZb|>k!-M;~LF~@8G literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/equal.png b/res/icons/graphics-window/equal.png new file mode 100644 index 0000000000000000000000000000000000000000..6037aed0f7c5cf33748e6540cd399a340777ae7a GIT binary patch literal 920 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZk1_nm=0G|-o|Ns9poM8|V z5fKs+`uOolWeRu8JrJ|yutE>Cu$&;9vm}%3d)zs9? zpFf|6hiAiv4Q6I$%a$$6$;k;848Ejw$=lm|?%cVnR;@BJGOCcMIHG<;l2y`y*Wrxj znL5cjBQ7I%clXqw)EB8QX3Ni(W0NZuFWzsl|8eZ&45>RJLV`W}ZHM+Re>PLqp^7g&R)ED@tHGc zczJo(tXY$ro7>XT^56KsxVZSun>R&8MOUm?abEMh0hd8iQc_=E-+JZs(bCZh3JMMm z4xyo;d3kxWX3a7%FbD_;NJvPiudiRfe*OOa`|sbs|9#^3O`A4#b#+x&SI5T2oYh7ML)4U=mE9R)O1bei-Owb+&&QI(KTSvuj!(o_YL z^=szrX;KOa@Qic~4dXo(c>O|1i}v+P*DhYY%x=uf)5|h3*TBrs)R^5+M@Mqa8o@_q z+jw}&4Epx2VU9U;k@M*4a|U9|yR|p9bj-G%qWXj}wZch5KVRD5?c;3bsUJSOcQ!US z%usYZsJJlkp_8Bhr-G!UsO(`WY4Q0J1mvgBm@l;{JKKy$gP=zW; zMM`2xs#P*bSt^5(fuWhMfr+k>QHY_Dm4T6!v8A?wffX>?&Q@EZXvob^$xN$+YB1C_ zvNS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+ z;1LNlky{9a886+f`vVkYFY)wsWq-gT#v{$~W$Myg1_nj}PZ!4!i_>?f*y_7DinPsF z-X6hgHnAhqu`}p>qqWKRV-%6PB*}tC zujidE7H`kj_v&kJEfhQU_N+QExWr0aBTAf$QWHxu^Yau!GILWI3=Q;+O!N&bH2GHl z2C7g6sYppINwrD_DNAKAGB7mLH89aNG72#?vNABTGPcw!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8Uop6XNRWe9f zDua=Mp_#6MiLQ}Rh@p{{fsvK5rM7{Am4QLP*=kD^4Y~O#nQ4_!4Tidg79j?vRz~Jl zh6WG~hTAJ90W~OsYzWRzD=AMbN@eg(OfJgLO-;#6RB+2IN-fVX$}Uzg(KC3Zrd|qE O%;4$j=d#Wzp$P!j<8{aY literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/image.png b/res/icons/graphics-window/image.png new file mode 100644 index 0000000000000000000000000000000000000000..d652d8e77c4297cc57816378acc0b9fa50ac3429 GIT binary patch literal 1025 zcmV+c1pfPpP)(_`g8%^e{{R4h=l}px2mk>USO5SzmjD14Z`WEM zkN^M!08mU+MgRZ*0L%dX{{9040|W#F1qB5L1_lQQ2M7oV2?+@b3JMDg3k(bl4Gj$r z4h|0w4-gO#5fKp*5)u;=6BHB_6%`d078Vy57Z?~A85tQG8X6lL8yp-Q9UUDW9v&Yb zA0QwgAt50mA|fLrBP1jwB_$;$CMG8*CnzW=DJdx`Dk>{0D=aK5EiElBE-o)GFEB7L zF)=YRGBPtWGc+_bH8nLhHa0gmH#j&rIXO8xIyyT$J3Kr*Jv}`>K0ZG`KR`f0K|w)6 zLPA4BLqtSGMMXtMMn*?RM@UFWNl8gcN=i#hOH52mO-)TsPEJoxPf$=$QBhG+Qc_b> zQ&dz`RaI41R#sP6S6EnBSy@?HT3TCMTU=aRU0q#XUS3~cUtnNhVPRonVq#-sV`OAx zWo2b%W@cw+XJ}|>X=!O{YHDk1Yiw+6ZEbCCZf7mzbEC znVFfInwp!No1C1Sot>SYo}QndpP-Ll?si~=| zs;aB2tE{Z7t*x!DuCA}IuduMNv9YnTva++Yv$V9dwY9ajwzjvox45{txw*Nzy1Ki& zyS%)-y}iA@zP`V|zreu2!NI}8!otJD!^FhI#l^+O#>U6T$H>UY$;rve%F4^j%goHo z&CSiu&d$%z&(P4&(b3V;($dq@)6~?|)z#J3*4Ee8*Vx$D+1c6J+S=RO+uYpT-QC^Z z-rnEe-{9cj;o;%p;^O1ulq(=H}<;=jiC@>FMd}>gwz3>+J08?d|RE?(XmJ z@9^;O@$vEU^78ZZ^Yrxe_4W1k_V)Mp_xSku`T6=PR2gr1-#QAO}rNS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5l;AAzh%9Dc;1&X5#!GkW z{s0BpOFVsD*&nco@kn!gnYuI=D5M_X6XN>+|9?I{J|Q6?4GoQ?q@_2pFf*}tJl&vJI3t`}!9tRer{LVjlLqG<-#D@|82f8aPdh*R zA<#mx64!_l=c3falFa-(g^eM1v{1B<+M7qx*ZR6#0I5=&C8l0nK+8H@}J z&2$Y+bd8Kc42`S|jI4|;wG9lc3=9I!R$HQI$jwj5Osj-yFw`})2r)3VGBUR^G=OL@ z++HyWs6i2ALvVgtNqJ&XDuZuga#4P6YD#9Jf?H-$YI%N9cCmtqp1~_M^-`c>22WQ% Jmvv4FO#prsoTUH& literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/lathe.png b/res/icons/graphics-window/lathe.png new file mode 100644 index 0000000000000000000000000000000000000000..8cee0583c28f0637c4a6c75a0847e021bb7ee696 GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaP3?%1DUd;wlEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5n0T@z%2~Ij105p zfJ|pE@$_|Nf50NfBhB$;>e5`G5Kn+lh^w5O-2eaoQ&Us_{P`1m>&*(FI8#ZGUoZnh z+2pepKpta~x4R4DeO=K1)orKarasJCedtPZ zX~ERzoQio8_m8}P!yEh9UUL4`J^z4Ksg}4#l%yn}$cBcl*Q vBP$~_DgTe~DWM4fT-A0G literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/length.png b/res/icons/graphics-window/length.png new file mode 100644 index 0000000000000000000000000000000000000000..b48715e094ccd65dd166f3fa9a61237624c5f752 GIT binary patch literal 480 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaN3?zjj6;1;wmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5ln@H=332@o1PuQ_FnkbT z5MVgN0OWmJYUKlzWh@Eu3ubV5b|VeMN%D4gVd!9$^#F1>3p^r=85p>QK$!8;-MT+O zLG}_)Usv`AEMh#;9ABm`%>@cYdAc};NL-$KY9nuh0gubYdLiaJ8yS5LaA&=SqCWC<(x*5oAN@BIr`zuJ2ydwWiNetEF% z@~9cF?p^()V=r_497oo422B~DEn+3E5hcz=sfi_-`FRQ{XE)7O># z0gD)qG{={zOLKujL7py-AsXkmPW0qEV8FrL96U+y=KudU>JHesWYx+n7HkqQOG^9Z z%NVH8R8>&$&hx{YIrI0K_IrQ${czr)?<;$&f33JP-|A^`=kFt$>-y{ESAJrOi(~Ya zF<2-LG+nI3HKN41C^fMpGe1uuBr`Xa!O%e8&_v(BB5&PAZJ-KOkcyPVl2of?kg`+; zBLhP-T>}$cBcl*QBP#^HVa@Dxn$-bqy^-3{0(z%&iO! zAQ}v}S4;wGPz2c!oS#-wo>-L1;G39Sl%JcLl9{OBmRXcqo?nz*tYD&N@Jda+6sVZN M)78&qol`;+0PNS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+ z;1LNlky{9a886+f`vVkYFY)wsWq-gT#v{$~W$Mygppb*7i(`nz>Eu7>9i$SJ0&QPP zMLf{)*>3pgdc!(~_U9Sz+XYuPvi8el`4|XYYEYJ9U$~roA={G#Rjq1czI{$W1IpOV z{urq$0?iUDag8W(E=o--$;{7F2+7P%WiT|*H#E^Vu*h3?Q5&d26{I31u_Vg zXWQ7=tX#R0hll6fxpTa{yo(ksa&~rhaBwIqD{F0SJ%0T7#EBC%G&IhiJRdC-T`O7J zF5O-wQROe-FT^6m&(Gh|(z1E;W(y08=Qp2EGM$tpk`yi&o+gquSAOmcxfv?#Djpsl zM~)mREiIirdvsOoHFdh=fJKZ)n&ZpVrMW<%%bqTdArg|U2Q$r? z5*ZpUwx@M?a835ms{U5%?YeY=O4J*X$VdO{O%7X{ygy@k*V|Zk?TpPHGbZ1@Wtjff z;lfG&Xz`{z>sfN{>n~ruZht`jsO<|!Q|KaZIWfq>iV%f4xh4U?GGiSSR z-r%u#>C*?Nv}SEBEwPcF9esO6*&mKKyYoUG+PX~4;JIP%AmZOLsePr1oBMI~nU{6k z(k;(@-f6)mK0QEV+Nq8w+N|sHjkb5ZdnYe9Un}(GPu9J+Uf;G}P~%Zr@=CR?R_wy> zMLY9jXGtCXXi$9-=smF#*N775qSVBa%=|oskj&gv215gVLlb=ii@bFgwSg*BK`K%b zOH!?pLCR7Yj0_CTbPY^&jf_GJjjRlefk@lHz{IaWiC>nC}Q!>*kp&ATz4J|?p zOs$N}tqcty8Vt8rOaf|91lbUrpH@~KsF=ai)z4*}Q$iB}3gla7 literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/parallel.png b/res/icons/graphics-window/parallel.png new file mode 100644 index 0000000000000000000000000000000000000000..fe89e2f100c33ae3ed352d0e318d0e502cfb2226 GIT binary patch literal 531 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkphQA|Pl)UP|Nj}zFo=kV zn3$Nv$Hxl^2^kt11_T7;<>hsEcduNz^5n^r*REaLy?ghhNs|&166)*g*RNlH{`~n5 zA3i*K^yui(qsx~s@9gZ%$;t8c_0`wc=i}p>KYxBhLxX{Vfq;NORJ5ud&@RT3AirP+ zhi5m^K%69RcNc~ZR#^`qhqJ&VvY3H^TL^?1FWs&C0~BO0@$_|Nf50NfBhB$;>e5`G zkiDmiV~EE2#-gQ0=Gp^3hM zMc%rL+CUYmAQdTzC8<`)AZ4ixMh1pvx&|h?Mn)lqMpgz!R>qdv1_o9J1_5WQEm1V& z=BH$)RYElw>Ka;v7?@fanOhkeKr|R`ub2eXpa`-dI6tkVJh3R1!8b9vC_gtfB{NaM gEwd=KJijQrSiwZk;FX$sDNr$kr>mdKI;Vst0KtQ(xBvhE literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/perpendicular.png b/res/icons/graphics-window/perpendicular.png new file mode 100644 index 0000000000000000000000000000000000000000..e9bf5d5f7493358a181cfa1c82c66970aced3094 GIT binary patch literal 427 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaP3?%1DUd;wlEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8Uop6XN>+|9^%v4F4H` z^#Aw2Zc72h8B2ovf*Bm1-ADs*lDyqr7&=&GJ%Aj}0*}aI1_o{+5N5n|x9$&6kiEpy z*OmPNix`hI$Cs%~bAdwIo-U3d9M_W*6nI=DB}5D~RHZZU6j6w{JtPG5-j4ibd z46FyW>IQ+eo=O@f{C8ND>d~}pkfA3S3j3^P6i9 zH9tT9$B9nYfmSn?1o;IsI6S+N2I3@nySp%Su*!M>Ih+L^k;M!Q+(IDCcQ}ahbkkL_5O(dMVyW`N@ZY4#w=bhdUKB#YABGSh& zVcJ2#Et`ryS)_<8DRgb`P~ziMO%;tgsvszoz`&5I#O#!G`iBD0a6j6w{JtPG5- zj4ibd46FyW>IQ+eo=O@f{C8ND>d~}pkfA3S3j3^P6!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8U)v6XN>+|9^%v4FCT9 zv%T`L6e!48666=m;PC858i21s zKVT8#k>>a^b!je8NY&HDF@)oKa>4=T2w4dU31*3i&0%Y!BpDtXHl|2vEZrd0%g(@{ zy^rTBYeb22QEFmIW`3SRNM>#-gQ0=Gp^3hMMc%rL+8`rADpC?lQmv9f z%2FAO3=GY54NP>6j6w{JtPG5-j4ibd46FyW>IQ+eo=O@f{C8ND>Zdc P7%_Od`njxgN@xNAk3Di5 literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/ref.png b/res/icons/graphics-window/ref.png new file mode 100644 index 0000000000000000000000000000000000000000..19f3b88a7c512fb185d6b3f224ef7a596617ad9d GIT binary patch literal 413 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaT3?y&uT)!Jgv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpaffhPl)S(AYk}^EP_K0 zD8g70rMq>1fP(BLp1!W^4_L%_ zq&dD!U78CNQucIl4B@z*oS?vv_3D+x92uDhJC?0$JIL&@+u>(lDc_GR%nbb!98Cvj zU5f-N7b|g%C~+=IO)SaG&r=A=%uQu5G|)FR(KoQjTX#_#s6rK_A|Er L^>bP0l+XkK^DcEc literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/same-orientation.png b/res/icons/graphics-window/same-orientation.png new file mode 100644 index 0000000000000000000000000000000000000000..850d2da8996482586e1c086848a01e947a6ddbb6 GIT binary patch literal 673 zcmY*TZAeoA6g{G%IcF(0rzTF&HCg7{rnRP|Z*FeOZJJxm!e~!@>a;n1KGCV=TneT? z63Lh#5`k1gQYZv{5~V>Ag8ryKhGAJqUqm`7!QP|(1oz&72&0ms00>P+ zT{(whz9U1q`TFLo7aWAukrooTb4jpGg>zg|Z7jC{ol?Np2mInB-zT7r0M?y=CIBQf zkKa3<&n?Ev%y#|G&JMI%6cr&o9sT`iY{bwIy1KwHa5_;{h166G4#H|hULFhv7>$rh zQCEkS7Kp`2OG85gQc_S~kN9{L6u@kT!vVD#G8sf7#Kyw!hf0OYO3*ZNb1^&&p%8L8 zSQZr(AW0-9A}I-8FKjkY6uP@nT51_|@K1)YDIDg@uzIXGnq4Rs^#%Bh4 zgFWi(&(WtpkipH3p1`S;VPy05CFSNXTeB$foZ1QeH)cn$z{|7U<+)so`dhUp!cERK@Rtyh}BoHMGQ@`E>Dxh?sC&06xoC-n>e!c z^!Ehk$lx6{6iaKWc^eHSp*WO9;3=**-JcqUD+S7 zi1A2se3`m57bsNZ>EaloaenJWPre2N4%d}YM=R$YU-jL7v2V`;ZS!A~^5imq1~inn z2*isXXqeBvOqEx2>D!DP?vsmOU*xstxan?aJ9UTJPm|L--2*qR&&s>Ep?%ZSkXZW- z@|N#RmOnY{I(LoN!?x{=dS$gA?yJ`Q+{(}QQHkZ^!b!$DK-PsvQHglaIyW>IQ+eo=O@f{C8ND>d~}pkfA3S3j3^P6%y{W;-5;PJ zdx@v7EBgZ$F&=4-FH@K10);$0T^vI+&L<}<&`LPMV8F>URm8)<&~T#O!81*V`NGV~ z_})w_E-L)|pg>e#em_Imb1n`p&c`3xH*lP2jIj0C#nRf?p}lAY3q$)4#^^KF2ikyU zij}xVlsFfqCYEI8=P86_=B6?j8t5CD=o?sQ@~!?2RG|t|k&;-FYLyI9mdapcU}&am zV4`be6k=#(Wnc_M+6D$z1_o09nN|taV5nT$ zP=g}KhT#0PlJdl&R0iL~|zBIJ%d+j>ZL%%44$rjF6*2U FngEJJn;!rG literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/step-rotate.png b/res/icons/graphics-window/step-rotate.png new file mode 100644 index 0000000000000000000000000000000000000000..22969f65fbc41248dac0cc9cea78066c32e7a7cb GIT binary patch literal 871 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpu~p&pAgso|Nr;*_uJds z3kV3zpFdw-UVh%Zd0)PKId|^d*RNkso;)clE4zLB_OoZtZrQTs;>C+MZro5)Q(L)m z<=eM!_4M@g_4Q}Wm~rRMoeLK(aC39NdGqGgt5@yq?c(C%XU?3_(9l@0V8Qh1)9dT& z<>cfhO`23&Tl@X{cYc2U-Me=$U%q_NqD4oK9_8cXd-v`g!x;tx1A|qoR?V6<>*dRr zFJ8QuIC0{>ef!RzKmYC9x7OCy*RNlfmzV46>S}9iPn$Nar>Do%)b#f4+fSc9ZEkKZ zDk@S{RlR@z{?)5jjg5_;KYxDx`t>76ju;skUAuPe=g*&geSLOzc5_b{X#oAsSQ6wH z%;50sMjD8d?NMQuIvw3#CW7RzD!-3 z3)I8x>Eak7A=!GcJ?M}DL&HOPVW-eVNx~C%2~4z72n;NEZ+riL?i$N=;qPqz+WiZj zr691$XW}ZCrk)NpQP!-iU8`6*Y=h^8K6w(;`}0Oaued?!$&RR;052WM-CNFh#R{Id zy4B6tj72T-_!%#wwWrwL9x+II?0d~hSD|;?Heu&;D%QILr~9m`p2j5odWynUmUEVM zGxQ$MDgJZH^0>r9$;YgX7d9?Ga6D>)U{(JA1Cu`gJ-oP)A?mQy@y_%k^XC*@%bXb4 zT&ySL{a~{9x^<$ByCNT!ot*sfuk|~55#>)8FTJV!P&j*ex~JdXIs0FU?ArC~+d7R2 zhaJ7tdhJw_?e=p7MLx^FKmVfmdasNq z-29Zxv`VN3LtR6Q5Cc;yBXcW51BeF0?G=-N8Wce`1m~xflqVLYGWaGY7v<-srer26 hxMdclmgg5`7b}?P8N5 literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/step-translate.png b/res/icons/graphics-window/step-translate.png new file mode 100644 index 0000000000000000000000000000000000000000..2901f9e52b4239d3d0adddb1500e484f38453212 GIT binary patch literal 411 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaP3?%1DUd;wlEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5n0T@z%2yAjF;}# z{Q(NHmw5WRvOi!E3hMC&cyt|Np;#|Ni&yACNKa%!_A0amJD$zhDN3 zXE)M7oFs2|7iK{opHD!Jw5N+>2*>s0ga+o-q0$ah7@C=cSVdhJnHLH$%s1gv%sDyb z0#KP)iEBiOb5UwyNoIbYLP%zADuaQczJam6f#v1Hzvlr}sDf0aB$lLFC4-cuG8h>c zn&}#t=o%S?7#dj_7z2^Efq|8Q!PE~Pt57uL=BH$)RYElw>Ka;v7?@fanOhkeKr|R` zub2eXpa`-dI6tkVJh3R1!8b9vC_gtfB{NaMEwd=KJijQrSiwZk;FX$sDNr$kr>mdK II;Vst0Jtl5rvLx| literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/symmetric.png b/res/icons/graphics-window/symmetric.png new file mode 100644 index 0000000000000000000000000000000000000000..e6e80ebe4b71647854ce2802c6e8e6b9ad2b58d4 GIT binary patch literal 515 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpoB+&Pl)UP|NjFR0_+*= z-5K0H7(C81oHu1Kb!Kor#&FDn!QukL1vdt_TMW0<8Pqp1Y%*Xluwk%S$FRluMA)RGyIolkY9OjaWBwJ#*!evUYh7ML)4jyNk9#XARB`7(@M${i&7bU6O)Vbb5m0?6BXPti&D$;i?WLqO!N$1si~I& P6*G9c`njxgN@xNAxfGbJ literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/tangent-arc.png b/res/icons/graphics-window/tangent-arc.png new file mode 100644 index 0000000000000000000000000000000000000000..d9dc40ac631cd3beedfed2da1ebe7fd5989a002f GIT binary patch literal 666 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpv3$DpAgsoK)~?d!NFn4 zk|jrv9^Jluds0$TUtizxySqEX8HUZ9H!obc(8Y zpf&%|H=vWmN?apKoQqNuOEUBG6hbm{QyB~m^bJk)4J`83UDO7uPz9+-Ni0dVN(L!Q zWiT=@G}ARO(KRv(F*LF=Fa{!R0|P4qgQ*`pR-tIf%}>cptAuJW)HSpSF)+0UNH%%K@ns_aDG}zd16s2gKuJTQGRY}N@k*hTV_#ed45rLv4V-7!7DX&P%JQb My85}Sb4q9e00j)-U;qFB literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/text.png b/res/icons/graphics-window/text.png new file mode 100644 index 0000000000000000000000000000000000000000..8923573ab0cbfc1e44c44f7bf6a95aaca7415f59 GIT binary patch literal 784 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1mUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+ z;1LNlky{9a886+f`vVkYFY)wsWq-gT#v{$~W$Myg1_s70o-U3d7N@69+M9hjP^4|X zv{lw>jwXdIyOwbsbtvqR%<64k*{oLQ(B1vwk^P0UN`6*5MUO6Q&d79)k?Kuau<`22 zYaOgd7r9PRiV%-g>36;yF*#nU`h3~>TFdj*@1#?T)wiAe9@v!lJhtFqWc#9H(c!ym zvfGu8>ts6bPjs3lq|Nvs@rK)>X&%f^8kgKX#Nxbm!nfaE6KAVU-BG;F;nXLQ2D>5$ z%Reaxcqj4|^Ly;};A)7TbK`{be9<(fIZ1Q!m$YgMz1gzzfeGt^!y^1ACNobvB;99b zy!UWrxwhqd?dqEJ-IGc$sV(c@a3-k1_ztUG%l9RXHgiiPJ55elT*#ax=e8%Q$MJ!? zMv>7e=jny-7@~I`3lIBoPC1Tgg1Ye2hm*D|Pud)HS#HJGTi05SOFor(`_5T`L5RbL zz2eZ#TMZjtEx&D8#S~+D__&OyHA^D__|vMbG)V zKxliwbLFQ!O3$1MljVN(0N=+=u%+FH@ z$;?e#iRWF{)OWfrBD=NDxcE12jRyi!vy1uACnboFyt=akR{0KFkHLjV8( literal 0 HcmV?d00001 diff --git a/res/icons/graphics-window/trim.png b/res/icons/graphics-window/trim.png new file mode 100644 index 0000000000000000000000000000000000000000..248e448ce7ef3c1584f872da0554d748607f6db7 GIT binary patch literal 575 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkphR+jPl)UP|Nj}zFi1#9 zoIQKi$;rvo)b#lA<0>jD$;rtpSFW5fV@7#-xq^a%fPg?&R@SOjt72ng4<9}}Yt}3c z4Glj(zY`};tX{oZOG|6Xk|l0#ZWAX?(7@~1LIpF}OL}SA>As!x|GddP25jSrX&Nz6m^K^k;UW*0)CYD1|adveg z&kw8-6c7<|D`mJLB^YQ}xaQx)2`UE{2SunZ*_oPRsN%p>^oi@mzDAFZWNwob6OmvNmLJWOL*3KSR=J}`U`U=WzVFku731_K6z{|x^hFg!?LNN8YaSirC#fFa-j!vO~dhgB=K zF9I6ESQ6wH%;50sMjD8d?NMQuIvw3 z#CW7RzD!-33ls|Tba4#PIKOwIqfmna53@L9cdqEy|NUPXOKcYIe3vpy?Zdh$mvjys zvR4pau+FGqTH~f(K2z5F&C>S*CNs$Ry$)QJ^(tM$JU;u6`$jqDolkeKOY^gS!MN9& zv3tq?s6e0%VkNE-CC){ui6xo&c?uz!xv30>2Kt63`UVzx>n>^oRj7hgq$HN4S|x*& zr7{>97@FxCnCKcAg%}!H85mg^TWT8^SQ!`uoUOJ*(U6;;l9^Ts)nKS=Xc1yyYGq_@ zWoQ7=V7R?v5>SI8$cEtjw370~qErUo#N?v<+|-oJLS?6Fq}hYU-sx O#SEUVelF{r5}E)JT%iU4 literal 0 HcmV?d00001 diff --git a/res/icons/text-window/constraint.png b/res/icons/text-window/constraint.png new file mode 100644 index 0000000000000000000000000000000000000000..bc1a7178a23da7acf244a22f5b9a4a0a94562a5f GIT binary patch literal 557 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkphQ%FPl)S(AYk|(EfhUZ zVP2lztAr?vs1=V@s9iiC zX!>!D_!@hljQC0 z!qCAg>jC6&7I;J!Gca%qfiUBxyLEqng6t)pzOL*KSj2b~1lRaHnhF$(_jGX#(Kw%+ zus}Y6h0%cdh}(1)Z3geeF5`sh2}jaIlsEal5aM~U?7`FnEIwy+*5tg2P_eMsxude; z=Z}rq!U7^f#}XHHXz1-e5>e>U;r(Dkn`01Hd&?Fcz37xo;p+@jw=urhrD*jAXq8xr zYeb22QEFmIW`3SRNM>#-gQ0=Gp^3hMMc%rL+CUYmAQdTzC8<`)AZ4ixMh1pvx&|h? zMn)lqhE}F#RwgFe1_o9J2LDzTi=k-9%}>cptAuJW)HSpSF)+0UNH%% zK@ns_aDG}zd16s2gKuJTQGRY}N@k*hTV_#ed45rLv4V-7!7DZOQlMf6Pgg&ebxsLQ E00-Qur~m)} literal 0 HcmV?d00001 diff --git a/res/icons/text-window/construction.png b/res/icons/text-window/construction.png new file mode 100644 index 0000000000000000000000000000000000000000..35ddcb274ff9113bfaca088234a5e6c7ac76b1d5 GIT binary patch literal 739 zcmY+BT}abm6vls%VRPw)lud20qSc09|NpkkjR?)nTsoO<(X5NMbk@qXG5@A3FqQQs zVi^>M85Ct?CVfDOP#IX*7k%7h7eQAM(N%P#751*W>EgVHbKd89-simMgu7;QdRk5z zknXIs*NLo2#^QzIe0S~TGm%num$}N|)=Blxz#>uW8Y=5taBvNDCSXpqIzL0J1Yi7M z8-V4}^Aoij#KnDgwbwB}KaU9xixPz51L%Nz5C=gjgf@5tBajO@Fbvrs^e5o}904=D z0HOFCmVq9sgabGY(~u8O;VKy6228;**a)T23lXru9vFh-uuWKmW;g}iQ1fSEyYLT3 z#Uvm;VGCS_N~pk5+Wt!nvOuY;@lg6{rYTwMs@iPl_#&-9po&)3w}3h`84KQi==dp` zsfyF(Nd1_SwIp}(_cK?*qIKJ8FY_8+HTZRC&JEQ2&S~VS_cPX#?D6sV!0X5H*-6Vo za!0IRyWM9`Nj;(FRncKXPgawxTUl6`v0Crz*RQ_vG4pNybmnMpRF%C{+k87ZyrM_j z`%T(rG+dhNcRF*b^RC~#_`|)`HMFzb)1Pi0o$=}G-)mF}!&H~1)1w|t-0d5?u<>Z3 z>tg?{6UK?{<`#8TUWZb|>k!-M;~LF~@8G literal 0 HcmV?d00001 diff --git a/res/icons/text-window/edges.png b/res/icons/text-window/edges.png new file mode 100644 index 0000000000000000000000000000000000000000..fcfd009ffde0fa487f4a55546c2a176e10c6dce8 GIT binary patch literal 703 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 literal 0 HcmV?d00001 diff --git a/res/icons/text-window/occluded-invisible.png b/res/icons/text-window/occluded-invisible.png new file mode 100644 index 0000000000000000000000000000000000000000..5ab4f82fbdf14b1145951f7597361a8157fbc840 GIT binary patch literal 345 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1rX+877Y2q^y~;*F9%q3^Bv6!F z2!t6g-L3lr6l5>)^mS!_z`@I*!e4%_eQ|$Sg3?z=mr%rK6N=ka< zwy0CDf;s_&a~Fv`qeB) zt7i>{!G3o*kNCtYJZO_tKKSpV_JIl2^CSxc48^Sy7>ng*Y*1el_lixx=%L|s`L>%^ zxE*S}oSv)k>ob2`5E3}~{;fCbvQ1OYNo*}RwAJDG#y>wc&RynNz;IRM!`kh?<)fRA zaU9^3s=ZTjkVi^t*SjgpB)Gkg`s^%eR2F-*eQtnQgT6_0+xbb)cONruNgdD{an^LB{Ts56mW#w literal 0 HcmV?d00001 diff --git a/res/icons/text-window/occluded-stippled.png b/res/icons/text-window/occluded-stippled.png new file mode 100644 index 0000000000000000000000000000000000000000..6d7a33f221f038d68881cacea2c318a0cea36dcb GIT binary patch literal 539 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1mUKs7M+SzC{oH>NS%G|oWRDEak-aeC_%Loa4Wk+%OQ zquh8{4jyu1;+mvX&7A+hR>!uff?tr^``{_=8}F_)P2g5pxg}`w`K0ta+l@DSbe+jA zlYc+E()xX}?5#ZBZEO4+tA9}$cBcl*QLn~7=D-#oK z0|P4qgMTZF#ZWZl=BH$)Rl+qGnuZtwH5gkNSU@!VFn#0&)WG2B>gTe~DWM4fTt2`@ literal 0 HcmV?d00001 diff --git a/res/icons/text-window/occluded-visible.png b/res/icons/text-window/occluded-visible.png new file mode 100644 index 0000000000000000000000000000000000000000..2ccd73e6c878d72326c415f20f7bb17ca3415603 GIT binary patch literal 365 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1rX+877Y2q^y~;*F9%q3^Bv6!F z2!t6g-L3lr6l5>)^mS!_z`@I*WK{VlMGYwQ&(p;*#NzbXDTaB690XkdKkAs~F;VFh zhvt$N<0lMvT9p^vVRjYqSkmFb%I=Y^=P;X3X{p+Q#UIw$##y_HOqj9qS@pcV)}K?` z|B3Pb6|Z=@J)lg&=iVp2634>68!X4Rt^e_%$TI%MrS3-dpl1stGK^Dw_3Ix!wTt)J zv6@kA$rhuv48L+De;t!&F7b$LE#tRP;Vk(Q>{Vi5?5#3g{DdaM#+Ycu^1h?7wi{M! zm@n0OsL7+(z3j>H8Rxf0a5^nM+}3#4wDl+3F{vp&%Oo~bO_0BxfBAgr9X*3*lQ$mo z(Y^aR^1j|K<GuF{3LGg{J{GPz>r|@boFyt I=akR{0Em&1x&QzG literal 0 HcmV?d00001 diff --git a/res/icons/text-window/outlines.png b/res/icons/text-window/outlines.png new file mode 100644 index 0000000000000000000000000000000000000000..2c07fba05227a1b74185efdcf65c7561be452e65 GIT binary patch literal 733 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1mUKs7M+SzC{oH>NS%G|oWRD-YjSD#gA|>t*phdU&VB zD(0z@YxRysIdPdCmtNN>dcLOY)uUZ}ecO%j;?k9y4rKjdkyKumbZ?I3=d>E0mB*&aA9?<7%9<@(0d}vtNU>5>8iapJ(oXD@Z4Eox<$-dvecw6><+_84o67SMi8y|FvckTu+o0l5fF#fzhp6;u=wsl30>zm0Xkxq!^4049#>6OmvNmLJSS9OwFuJOtcLQ xtPBkPtt=Kp(U6;;l9^Ts*I;NGVg%G+Y-L~p(eT6ckrz+{gQu&X%Q~loCIJ4IC$|6q literal 0 HcmV?d00001 diff --git a/res/icons/text-window/point.png b/res/icons/text-window/point.png new file mode 100644 index 0000000000000000000000000000000000000000..421688105dbb6739e62266c82ae3c4894d3a29a2 GIT binary patch literal 394 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaT3?y&uT)!Jgv7|ftIx;Y9?C1WI$O_~uBzpw; zGB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpaffhPl)UP|Nj}zFc|fE zgG3lhg8YIR9G=}s196hP-CYt=nRkbtL)V+hCf5XRI6l=vQ!2m14A=i0~1{%qYy(QD+4ntBMWT< z11kdqsCm701fP%(q2tDnm{r-UW|*wAW- literal 0 HcmV?d00001 diff --git a/res/icons/text-window/shaded.png b/res/icons/text-window/shaded.png new file mode 100644 index 0000000000000000000000000000000000000000..0dff1ae182f995557e6eec337a170edfa6d37655 GIT binary patch literal 403 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaP3?%1DUd;wlEa{HEjtmUzPnffIy#(?Vl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=dY0)7O>#0gD)qf{>}} zW__R#Pk>K|>;M1%&CSjI{QT(Bjqr3(~iED7=pW^j0RBMrn!@^*J&=wOxg0CIvn zT^vI=t|uonG+tm3I4Sju@l5A`hG&1+1803q@XEI3yb{VGDka6VWM5m`HHMWJE?ltZ ziSl-Ke%yFvRp`S12d`vFd}VYpG%#S)+@QT9>~JhQL-Hic-zWIi-U6DgTH+c}l9E`G zYL#4+3Zxi}3=GY54NP>6j6w_ztxV0VOiZ*546FkYez3 L^>bP0l+XkKgaUcx literal 0 HcmV?d00001 diff --git a/res/icons/text-window/workplane.png b/res/icons/text-window/workplane.png new file mode 100644 index 0000000000000000000000000000000000000000..7df689757b247f538a10c5b203b22311c61a3d74 GIT binary patch literal 462 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaN3?zjj6;1;wmUKs7M+S!VC(K#9UIO_F$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8XLh>Fdh=fJKZ)LCDl~ zvp!HrCBP@d_5c6>f`WodN=nAY#x5={p`oE^X=&x<<*lu)9UUDjR;<{uV~1HT?{T1d z#*!evUYh7ML)4(zT=OljGHCHLX-zdYp6Jwcky&>R!;}c2TXQUv`hgBm zEpd$~Nl7e8wMs5Z1yT$~28L$31}3^jMj?iVR;FfFCMMbj237_J|5g@@p=ij>PsvQH zglaI3VtQ&&YGO)d T;mK4`ATxNn`njxgN@xNAcv6Wz literal 0 HcmV?d00001 diff --git a/res/locales.txt b/res/locales.txt new file mode 100644 index 0000000..0f4fab1 --- /dev/null +++ b/res/locales.txt @@ -0,0 +1,8 @@ +# This file lists the ISO locale codes (ISO 639-1/ISO 3166-1), Windows LCIDs, +# and human-readable names for every culture supported by SolveSpace. +de-DE,0407,Deutsch +en-US,0409,English (US) +fr-FR,040C,Français +ru-RU,0419,Русский +uk-UA,0422,Українська +zh-CN,0804,简体中文 diff --git a/res/locales/de_DE.po b/res/locales/de_DE.po new file mode 100644 index 0000000..bf858b1 --- /dev/null +++ b/res/locales/de_DE.po @@ -0,0 +1,2021 @@ +# German translations for the SolveSpace package. +# Copyright (C) 2018 the SolveSpace authors +# This file is distributed under the same license as the SolveSpace package. +# Guido Hoss , 2018. #zanata +msgid "" +msgstr "" +"Project-Id-Version: SolveSpace 3.0\n" +"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" +"POT-Creation-Date: 2020-11-17 20:50-0500\n" +"PO-Revision-Date: 2018-07-19 06:55+0000\n" +"Last-Translator: Reini Urban \n" +"Language-Team: none\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Zanata 4.5.0\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +#: clipboard.cpp:274 +msgid "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." +msgstr "" +"Ausschneiden, Einfügen und Kopieren sind nur in einer Arbeitsebene " +"zulässig.\n" +"\n" +"Aktivieren Sie eine mit Skizze -> In Arbeitsebene" + +#: clipboard.cpp:291 +msgid "Clipboard is empty; nothing to paste." +msgstr "Zwischenablage ist leer; es gibt nichts einzufügen." + +#: clipboard.cpp:338 +msgid "Number of copies to paste must be at least one." +msgstr "Die Anzahl der einzufügenden Kopien muss mind. 1 sein." + +#: clipboard.cpp:354 textscreens.cpp:783 +msgid "Scale cannot be zero." +msgstr "Maßstab kann nicht Null sein." + +#: clipboard.cpp:396 +msgid "Select one point to define origin of rotation." +msgstr "Wählen Sie einen Punkt, um den Drehmittelpunkt zu definieren." + +#: clipboard.cpp:408 +msgid "Select two points to define translation vector." +msgstr "Wählen Sie zwei Punkte, um den Verschiebungsvektor zu definieren." + +#: clipboard.cpp:418 +msgid "" +"Transformation is identity. So all copies will be exactly on top of each " +"other." +msgstr "" +"Die Transformation ist die Identität. Alle Kopien werden deckungsgleich " +"übereinanderliegen." + +#: clipboard.cpp:422 +msgid "Too many items to paste; split this into smaller pastes." +msgstr "" +"Zuviele Objekte zum Einfügen; teilen Sie diese in kleinere " +"Einfügeoperationen auf." + +#: clipboard.cpp:427 +msgid "No workplane active." +msgstr "Es ist keine Arbeitsebene aktiv." + +#: confscreen.cpp:410 +msgid "Bad format: specify coordinates as x, y, z" +msgstr "Ungültiges Format: geben Sie Koordinaten als x, y, z an" + +#: confscreen.cpp:420 style.cpp:659 textscreens.cpp:805 +msgid "Bad format: specify color as r, g, b" +msgstr "Ungültiges Format: geben Sie Farben als r, g, b an" + +#: confscreen.cpp:446 +msgid "" +"The perspective factor will have no effect until you enable View -> Use " +"Perspective Projection." +msgstr "" +"Der Perspektivfaktor wird sich nicht auswirken, bis Sie Ansicht -> " +"Perspektive Projektion aktivieren." + +#: confscreen.cpp:459 confscreen.cpp:469 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "Geben Sie 0 bis %d Ziffern nach dem Dezimalzeichen an." + +#: confscreen.cpp:481 +msgid "Export scale must not be zero!" +msgstr "Der Exportmaßstab darf nicht Null sein!" + +#: confscreen.cpp:493 +msgid "Cutter radius offset must not be negative!" +msgstr "Der Werkzeugradialabstand darf nicht negativ sein!" + +#: confscreen.cpp:547 +msgid "Bad value: autosave interval should be positive" +msgstr "" +"Ungültiger Wert: Interval für automatisches Speichern muss positiv sein" + +#: confscreen.cpp:550 +msgid "Bad format: specify interval in integral minutes" +msgstr "Ungültiges Format: geben Sie das Interval in ganzen Minuten an" + +#: constraint.cpp:12 +msgctxt "constr-name" +msgid "pts-coincident" +msgstr "Pkt-Deckung" + +#: constraint.cpp:13 +msgctxt "constr-name" +msgid "pt-pt-distance" +msgstr "Pkt-Pkt-Abstand" + +#: constraint.cpp:14 +msgctxt "constr-name" +msgid "pt-line-distance" +msgstr "Pkt-Linie-Abstand" + +#: constraint.cpp:15 +msgctxt "constr-name" +msgid "pt-plane-distance" +msgstr "Pkt-Ebene-Abstand" + +#: constraint.cpp:16 +msgctxt "constr-name" +msgid "pt-face-distance" +msgstr "Pkt-Fläche-Abstand" + +#: constraint.cpp:17 +msgctxt "constr-name" +msgid "proj-pt-pt-distance" +msgstr "Proj-Pkt-Pkt-Abstand" + +#: constraint.cpp:18 +msgctxt "constr-name" +msgid "pt-in-plane" +msgstr "Pkt-in-Ebene" + +#: constraint.cpp:19 +msgctxt "constr-name" +msgid "pt-on-line" +msgstr "Pkt-auf-Linie" + +#: constraint.cpp:20 +msgctxt "constr-name" +msgid "pt-on-face" +msgstr "Pkt-auf-Fläche" + +#: constraint.cpp:21 +msgctxt "constr-name" +msgid "eq-length" +msgstr "gl-Länge" + +#: constraint.cpp:22 +msgctxt "constr-name" +msgid "eq-length-and-pt-ln-dist" +msgstr "gl-Länge-und-Pkt-Linie-Abstand" + +#: constraint.cpp:23 +msgctxt "constr-name" +msgid "eq-pt-line-distances" +msgstr "gl-Pkt-Linie-Abstände" + +#: constraint.cpp:24 +msgctxt "constr-name" +msgid "length-ratio" +msgstr "Längenverhältnis" + +#: constraint.cpp:25 +msgctxt "constr-name" +msgid "length-difference" +msgstr "Längendifferenz" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "symmetric" +msgstr "Symmetrisch" + +#: constraint.cpp:27 +msgctxt "constr-name" +msgid "symmetric-h" +msgstr "Symmetrisch-H" + +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "symmetric-v" +msgstr "Symmetrisch-V" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "symmetric-line" +msgstr "Symmetrisch-Linie" + +#: constraint.cpp:30 +msgctxt "constr-name" +msgid "at-midpoint" +msgstr "auf-Mittelpunkt" + +#: constraint.cpp:31 +msgctxt "constr-name" +msgid "horizontal" +msgstr "Horizontal" + +#: constraint.cpp:32 +msgctxt "constr-name" +msgid "vertical" +msgstr "Vertikal" + +#: constraint.cpp:33 +msgctxt "constr-name" +msgid "diameter" +msgstr "Durchmesser" + +#: constraint.cpp:34 +msgctxt "constr-name" +msgid "pt-on-circle" +msgstr "Pkt-auf-Kreis" + +#: constraint.cpp:35 +msgctxt "constr-name" +msgid "same-orientation" +msgstr "gl-Orientierung" + +#: constraint.cpp:36 +msgctxt "constr-name" +msgid "angle" +msgstr "Winkel" + +#: constraint.cpp:37 +msgctxt "constr-name" +msgid "parallel" +msgstr "Parallel" + +#: constraint.cpp:38 +msgctxt "constr-name" +msgid "arc-line-tangent" +msgstr "Bogen-Linie-Tangente" + +#: constraint.cpp:39 +msgctxt "constr-name" +msgid "cubic-line-tangent" +msgstr "Kub-Linie-Tangente" + +#: constraint.cpp:40 +msgctxt "constr-name" +msgid "curve-curve-tangent" +msgstr "Kurve-Kurve-Tangente" + +#: constraint.cpp:41 +msgctxt "constr-name" +msgid "perpendicular" +msgstr "Rechtwinklig" + +#: constraint.cpp:42 +msgctxt "constr-name" +msgid "eq-radius" +msgstr "gl-Radius" + +#: constraint.cpp:43 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "gl-Winkel" + +#: constraint.cpp:44 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "gl-Linie-Länge-Bogen-Länge" + +#: constraint.cpp:45 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "Fix-an-Position" + +#: constraint.cpp:46 +msgctxt "constr-name" +msgid "comment" +msgstr "Kommentar" + +#: constraint.cpp:171 +msgid "" +"Bad selection for distance / diameter constraint. This constraint can apply " +"to:\n" +"\n" +" * two points (distance between points)\n" +" * a line segment (length)\n" +" * two points and a line segment or normal (projected distance)\n" +" * a workplane and a point (minimum distance)\n" +" * a line segment and a point (minimum distance)\n" +" * a plane face and a point (minimum distance)\n" +" * a circle or an arc (diameter)\n" +msgstr "" +"Ungültige Auswahl für Einschränkung Abstand / Durchmesser Diese " +"Einschränkung ist anwendbar auf:\n" +"\n" +" * zwei Punkte [Abstand zwischen Punkten]\n" +" * ein Liniensegment [Länge]\n" +" * zwei Punkte und ein Liniensegment oder Normale [projizierter Abstand]\n" +" * eine Arbeitsebene und ein Punkt [minimaler Abstand] \n" +" * ein Liniensegment und ein Punkt [minimaler Abstand]\n" +" * eine Seitenfläche und ein Punkt [minimaler Abstand]\n" +" * ein Kreis oder ein Bogen [Durchmesser]\n" + +#: constraint.cpp:224 +msgid "" +"Bad selection for on point / curve / plane constraint. This constraint can " +"apply to:\n" +"\n" +" * two points (points coincident)\n" +" * a point and a workplane (point in plane)\n" +" * a point and a line segment (point on line)\n" +" * a point and a circle or arc (point on curve)\n" +" * a point and a plane face (point on face)\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"Auf Punkt / Kurve / Ebene\". Diese " +"Einschränkung ist anwendbar auf:\n" +"\n" +" * zwei Punkte [deckungsgleich]\n" +" * einen Punkt und eine Arbeitsebene [Punkt auf Ebene]\n" +" * einen Punkt und ein Liniensegment [Punkt auf Linie]\n" +" * einen Punkt und einen Kreis oder Bogen [Punkt auf Kurve]\n" +" * einen Punkt und eine Seitenfläche [Punkt auf Fläche]\n" + +#: constraint.cpp:286 +msgid "" +"Bad selection for equal length / radius constraint. This constraint can " +"apply to:\n" +"\n" +" * two line segments (equal length)\n" +" * two line segments and two points (equal point-line distances)\n" +" * a line segment and two points (equal point-line distances)\n" +" * a line segment, and a point and line segment (point-line distance " +"equals length)\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" +" * two circles or arcs (equal radius)\n" +" * a line segment and an arc (line segment length equals arc length)\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"gleicher Abstand / Radius\". Diese " +"Einschränkung ist anwendbar auf:\n" +"\n" +" * zwei Liniensegmente [gleiche Länge]\n" +" * zwei Liniensegmente und zwei Punkte [gleiche Punkt-Linien-Abstände]\n" +" * ein Liniensegment und zwei Punkte [gleiche Punkt-Linien-Abstände]\n" +" * ein Liniensegment und ein Punkt oder Liniensegment [Abstand Punkt-" +"Linie gleich Länge]\n" +" * vier Liniensegmente oder Normale [gleicher Winkel zwischen A,B und C," +"D]\n" +" * drei Liniensegmente oder Normale [gleicher Winkel zwischen A,B und B," +"C]\n" +" * zwei Kreise oder Bögen [gleicher Radius]\n" +" * ein Liniensegment und ein Bogen [Länge des Liniensegments gleich " +"Bogenlänge]\n" + +#: constraint.cpp:325 +msgid "" +"Bad selection for length ratio constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"Längenverhältnis\". Diese " +"Einschränkung ist anwendbar auf:\n" +"\n" +" * zwei Liniensegmente\n" + +#: constraint.cpp:342 +msgid "" +"Bad selection for length difference constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"Längendifferenz\". Diese Einschränkung " +"ist anwendbar auf:\n" +"\n" +" * zwei Liniensegmente\n" + +#: constraint.cpp:368 +msgid "" +"Bad selection for at midpoint constraint. This constraint can apply to:\n" +"\n" +" * a line segment and a point (point at midpoint)\n" +" * a line segment and a workplane (line's midpoint on plane)\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"Mittelpunkt\". Diese Einschränkung ist " +"anwendbar auf:\n" +"\n" +" * ein Liniensegment und ein Punkt [Punkt auf Mittelpunkt]\n" +" * ein Liniensegment und eine Arbeitsebene [Mittelpunkt der Linie auf " +"Ebene]\n" + +#: constraint.cpp:426 +msgid "" +"Bad selection for symmetric constraint. This constraint can apply to:\n" +"\n" +" * two points or a line segment (symmetric about workplane's coordinate " +"axis)\n" +" * line segment, and two points or a line segment (symmetric about line " +"segment)\n" +" * workplane, and two points or a line segment (symmetric about " +"workplane)\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"Symmetrisch\". Diese Einschränkung ist " +"anwendbar auf:\n" +"\n" +" * zwei Punkte oder ein Liniensegment [symmetrisch zu Koordinatenachse " +"der Arbeitsebene]\n" +" * ein Liniensegment und zwei Punkte oder ein Liniensegment [symmetrisch " +"zu Liniensegment]\n" +" * eine Arbeitsebene und zwei Punkte oder ein Liniensegment [symmetrisch " +"zu Arbeitsebene]\n" + +#: constraint.cpp:440 +msgid "" +"A workplane must be active when constraining symmetric without an explicit " +"symmetry plane." +msgstr "" +"Eine Arbeitsebene muss aktiv sein, um die Symmetrie ohne explizite " +"Symmetrieebene einzuschränken." + +#: constraint.cpp:470 +msgid "" +"Activate a workplane (with Sketch -> In Workplane) before applying a " +"horizontal or vertical constraint." +msgstr "" +"Aktivieren Sie eine Arbeitsebene (mit Skizze -> In Arbeitsebene), bevor Sie " +"horizontal oder vertikal einschränken." + +#: constraint.cpp:483 +msgid "" +"Bad selection for horizontal / vertical constraint. This constraint can " +"apply to:\n" +"\n" +" * two points\n" +" * a line segment\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"horizontal / vertikal\". Diese " +"Einschränkung ist anwendbar auf:\n" +"\n" +" * zwei Punkte\n" +" * ein Liniensegment\n" + +#: constraint.cpp:504 +msgid "" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" +"\n" +" * two normals\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"gleiche Orientierung\". Diese " +"Einschränkung ist anwendbar auf:\n" +"\n" +" * zwei Normale\n" + +#: constraint.cpp:554 +msgid "Must select an angle constraint." +msgstr "Sie müssen einen eingeschränkten Winkel auswählen." + +#: constraint.cpp:567 +msgid "Must select a constraint with associated label." +msgstr "Sie müssen eine Einschränkung mit zugeordneter Kennzeichnung angeben." + +#: constraint.cpp:578 +msgid "" +"Bad selection for angle constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"Winkel\". Diese Einschränkung ist " +"anwendbar auf:\n" +"\n" +" * zwei Liniensegmente\n" +" * ein Liniensegment und eine Normale\n" +" * zwei Normale\n" + +#: constraint.cpp:635 +msgid "" +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent." +msgstr "" +"Die Bogentangente und das Liniensegment müssen einen gemeinsamen Endpunkt " +"haben. Schränken Sie mit \"Einschränkung / Auf Punkt\" ein, bevor Sie die " +"Tangente einschränken. -> Sc" + +#: constraint.cpp:659 +msgid "" +"The tangent cubic and line segment must share an endpoint. Constrain them " +"with Constrain -> On Point before constraining tangent." +msgstr "" +"Die Kurventangente und das Liniensegment müssen einen gemeinsamen Endpunkt " +"haben. Schränken Sie mit \"Einschränkung / Auf Punkt\" ein, bevor Sie die " +"Tangente einschränken. -> Sc" + +#: constraint.cpp:669 +msgid "Curve-curve tangency must apply in workplane." +msgstr "" +"Die Kurven-Kurven-Tangente muss in der Arbeitsebene eingeschränkt werden." + +#: constraint.cpp:687 +msgid "" +"The curves must share an endpoint. Constrain them with Constrain -> On Point " +"before constraining tangent." +msgstr "" +"Die Kurven müssen einen gemeinsamen Endpunkt haben. Schränken Sie mit " +"\"Einschränkung / Auf Punkt\" ein, bevor Sie die Tangente einschränken. -> Sc" + +#: constraint.cpp:696 +msgid "" +"Bad selection for parallel / tangent constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments (parallel)\n" +" * a line segment and a normal (parallel)\n" +" * two normals (parallel)\n" +" * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"Parallel / Tangente\". Diese " +"Einschränkung ist anwendbar auf:\n" +"\n" +" * zwei Liniensegmente [parallel]\n" +" * ein Liniensegment und eine Normale [parallel]\n" +" * zwei Normalen [parallel]\n" +" * zwei Liniensegmente, Bögen oder Beziers mit gemeinsamem Endpunkt " +"[Tangente]\n" + +#: constraint.cpp:714 +msgid "" +"Bad selection for perpendicular constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"Rechtwinklig\". Diese Einschränkung " +"ist anwendbar auf:\n" +"\n" +" * zwei Liniensegmente\n" +" * ein Liniensegment und eine Normale\n" +" * zwei Normale\n" + +#: constraint.cpp:729 +msgid "" +"Bad selection for lock point where dragged constraint. This constraint can " +"apply to:\n" +"\n" +" * a point\n" +msgstr "" +"Ungültige Auswahl für Einschränkung \"Punkt an Position\". Diese " +"Einschränkung ist anwendbar auf:\n" +"\n" +" * einen Punkt\n" + +#: constraint.cpp:740 +msgid "click center of comment text" +msgstr "Klicken Sie auf die Mitte des Kommentartextes" + +#: export.cpp:19 +msgid "" +"No solid model present; draw one with extrudes and revolves, or use Export " +"2d View to export bare lines and curves." +msgstr "" +"Kein Festkörper vorhanden; zeichnen Sie eines mit Extrusionen und Drehungen, " +"oder exportieren Sie bloße Linien und Kurven mit \"2D-Ansicht exportieren\"" + +#: export.cpp:61 +msgid "" +"Bad selection for export section. Please select:\n" +"\n" +" * nothing, with an active workplane (workplane is section plane)\n" +" * a face (section plane through face)\n" +" * a point and two line segments (plane through point and parallel to " +"lines)\n" +msgstr "" +"Ungültige Auswahl für teilweisen Export. Bitte wählen Sie aus:\n" +"\n" +" * nichts, mit einer aktiven Arbeitsebene [Arbeitsebene ist " +"Schnittebene]\n" +" * eine Seitenfläche [Schnittebene durch Seitenfläche]\n" +" * einen Punkt und zwei Liniensegmente [Schnittebene durch Punkt und " +"parallel zu Linien]\n" + +#: export.cpp:822 +msgid "Active group mesh is empty; nothing to export." +msgstr "Das Netz der aktiven Gruppe ist leer; es gibt nichts zu exportieren." + +#: exportvector.cpp:337 +msgid "freehand lines were replaced with continuous lines" +msgstr "Freihandlinien wurden mit durchgehenden Linien ersetzt" + +#: exportvector.cpp:339 +msgid "zigzag lines were replaced with continuous lines" +msgstr "Zickzacklinien wurden mit durchgehenden Linien ersetzt" + +#: exportvector.cpp:593 +msgid "" +"Some aspects of the drawing have no DXF equivalent and were not exported:\n" +msgstr "" +"Teile der Zeichnung haben keine Entsprechung in DXF und wurden nicht " +"exportiert:\n" + +#: exportvector.cpp:839 +msgid "" +"PDF page size exceeds 200 by 200 inches; many viewers may reject this file." +msgstr "" +"Die PDF-Seitengröße überschreitet 200 x 200 Zoll; die Datei kann u.U. in " +"vielen Programmen nicht geöffnet werden." + +#: file.cpp:44 group.cpp:91 +msgctxt "group-name" +msgid "sketch-in-plane" +msgstr "Skizze-in-Ebene" + +#: file.cpp:62 +msgctxt "group-name" +msgid "#references" +msgstr "#Referenzen" + +#: file.cpp:549 +msgid "" +"Unrecognized data in file. This file may be corrupt, or from a newer version " +"of the program." +msgstr "" +"Nicht erkannte Daten in der Datei. Diese Datei könnte beschädigt sein oder " +"von einer neueren Version des Programms stammen." + +#: file.cpp:859 +msgctxt "title" +msgid "Missing File" +msgstr "Fehlende Datei" + +#: file.cpp:860 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "Die verlinkte Datei “%s” fehlt." + +#: file.cpp:862 +msgctxt "dialog" +msgid "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be " +"permanently removed." +msgstr "" +"Möchten Sie sie selber auswählen?\n" +"Falls Sie ablehnen, wird jegliche mit der fehlenden Datei verknüpfte " +"Geometrie verworfen." + +#: file.cpp:865 +msgctxt "button" +msgid "&Yes" +msgstr "&Ja" + +#: file.cpp:867 +msgctxt "button" +msgid "&No" +msgstr "&Nein" + +#: file.cpp:869 +msgctxt "button" +msgid "&Cancel" +msgstr "&Abbrechen" + +#: graphicswin.cpp:41 +msgid "&File" +msgstr "&Datei" + +#: graphicswin.cpp:42 +msgid "&New" +msgstr "&Neu" + +#: graphicswin.cpp:43 +msgid "&Open..." +msgstr "&Öffnen" + +#: graphicswin.cpp:44 +msgid "Open &Recent" +msgstr "Öffne &letzte" + +#: graphicswin.cpp:45 +msgid "&Save" +msgstr "&Speichern" + +#: graphicswin.cpp:46 +msgid "Save &As..." +msgstr "Speichern &Als…" + +#: graphicswin.cpp:48 +msgid "Export &Image..." +msgstr "Exportiere &Bild…" + +#: graphicswin.cpp:49 +msgid "Export 2d &View..." +msgstr "Exportiere 2D-Ansicht…" + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "Exportiere 2D-Auswahl…" + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "Exportiere 3D-Drahtgittermodell" + +#: graphicswin.cpp:52 +msgid "Export Triangle &Mesh..." +msgstr "Exportiere Dreiecksnetz…" + +#: graphicswin.cpp:53 +msgid "Export &Surfaces..." +msgstr "Exportiere Oberflächen…" + +#: graphicswin.cpp:54 +msgid "Im&port..." +msgstr "Im&port…" + +#: graphicswin.cpp:57 +msgid "E&xit" +msgstr "Beenden" + +#: graphicswin.cpp:60 +msgid "&Edit" +msgstr "&Bearbeiten" + +#: graphicswin.cpp:61 +msgid "&Undo" +msgstr "&Rückgängig machen" + +#: graphicswin.cpp:62 +msgid "&Redo" +msgstr "&Wiederholen" + +#: graphicswin.cpp:63 +msgid "Re&generate All" +msgstr "Alles &neu zeichnen" + +#: graphicswin.cpp:65 +msgid "Snap Selection to &Grid" +msgstr "Auswahl auf &Raster ausrichten" + +#: graphicswin.cpp:66 +msgid "Rotate Imported &90°" +msgstr "Importierte Objekte &90° drehen" + +#: graphicswin.cpp:68 +msgid "Cu&t" +msgstr "Ausschneiden" + +#: graphicswin.cpp:69 +msgid "&Copy" +msgstr "Kopieren" + +#: graphicswin.cpp:70 +msgid "&Paste" +msgstr "Einfügen" + +#: graphicswin.cpp:71 +msgid "Paste &Transformed..." +msgstr "&Transformiert einfügen…" + +#: graphicswin.cpp:72 +msgid "&Delete" +msgstr "&Löschen" + +#: graphicswin.cpp:74 +msgid "Select &Edge Chain" +msgstr "&Kantenverlauf auswählen" + +#: graphicswin.cpp:75 +msgid "Select &All" +msgstr "&Alle auswählen" + +#: graphicswin.cpp:76 +msgid "&Unselect All" +msgstr "Alle &deselektieren" + +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "&Linien Stile..." + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "An&sichts Projektion..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "&Einstellungen..." + +#: graphicswin.cpp:84 +msgid "&View" +msgstr "Ansicht" + +#: graphicswin.cpp:85 +msgid "Zoom &In" +msgstr "Zoom größer" + +#: graphicswin.cpp:86 +msgid "Zoom &Out" +msgstr "Zoom kleiner" + +#: graphicswin.cpp:87 +msgid "Zoom To &Fit" +msgstr "Zoom anpassen" + +#: graphicswin.cpp:89 +msgid "Align View to &Workplane" +msgstr "Ansicht auf Arbeitsebene ausrichten" + +#: graphicswin.cpp:90 +msgid "Nearest &Ortho View" +msgstr "Nächste Ortho-Ansicht" + +#: graphicswin.cpp:91 +msgid "Nearest &Isometric View" +msgstr "Nächste isometrische Ansicht" + +#: graphicswin.cpp:92 +msgid "&Center View At Point" +msgstr "Ansicht auf Punkt zentrieren" + +#: graphicswin.cpp:94 +msgid "Show Snap &Grid" +msgstr "Arbeitsraster anzeigen" + +#: graphicswin.cpp:95 +msgid "Use &Perspective Projection" +msgstr "Perspektivische Projektion" + +#: graphicswin.cpp:96 +msgid "Dimension &Units" +msgstr "Maßeinheit" + +#: graphicswin.cpp:97 +msgid "Dimensions in &Millimeters" +msgstr "Maße in Millimeter" + +#: graphicswin.cpp:98 +msgid "Dimensions in M&eters" +msgstr "Masse in M&etern" + +#: graphicswin.cpp:99 +msgid "Dimensions in &Inches" +msgstr "Maße in Zoll" + +#: graphicswin.cpp:101 +msgid "Show &Toolbar" +msgstr "Werkzeugleiste anzeigen" + +#: graphicswin.cpp:102 +msgid "Show Property Bro&wser" +msgstr "Attributbrowser anzeigen" + +#: graphicswin.cpp:104 +msgid "&Full Screen" +msgstr "Vollbildschirm" + +#: graphicswin.cpp:106 +msgid "&New Group" +msgstr "Neue Gruppe" + +#: graphicswin.cpp:107 +msgid "Sketch In &3d" +msgstr "In 3D skizzieren" + +#: graphicswin.cpp:108 +msgid "Sketch In New &Workplane" +msgstr "In neuer Arbeitsebene skizzieren" + +#: graphicswin.cpp:110 +msgid "Step &Translating" +msgstr "Kopieren und verschieben" + +#: graphicswin.cpp:111 +msgid "Step &Rotating" +msgstr "Kopieren und drehen" + +#: graphicswin.cpp:113 +msgid "E&xtrude" +msgstr "E&xtrudieren" + +#: graphicswin.cpp:114 +msgid "&Helix" +msgstr "&Helix" + +#: graphicswin.cpp:115 +msgid "&Lathe" +msgstr "R&otieren" + +#: graphicswin.cpp:116 +msgid "Re&volve" +msgstr "D&rehen" + +#: graphicswin.cpp:118 +msgid "Link / Assemble..." +msgstr "Verknüpfen / Zusammensetzen" + +#: graphicswin.cpp:119 +msgid "Link Recent" +msgstr "Letzte verknüpfen" + +#: graphicswin.cpp:121 +msgid "&Sketch" +msgstr "&Skizze" + +#: graphicswin.cpp:122 +msgid "In &Workplane" +msgstr "In Arbeitsebene" + +#: graphicswin.cpp:123 +msgid "Anywhere In &3d" +msgstr "Im 3D-Raum" + +#: graphicswin.cpp:125 +msgid "Datum &Point" +msgstr "Bezugspunkt" + +#: graphicswin.cpp:126 +msgid "&Workplane" +msgstr "Arbeits&ebene" + +#: graphicswin.cpp:128 +msgid "Line &Segment" +msgstr "&Linie" + +#: graphicswin.cpp:129 +msgid "C&onstruction Line Segment" +msgstr "K&onstruktionslinie" + +#: graphicswin.cpp:130 +msgid "&Rectangle" +msgstr "&Rechteck" + +#: graphicswin.cpp:131 +msgid "&Circle" +msgstr "&Kreis" + +#: graphicswin.cpp:132 +msgid "&Arc of a Circle" +msgstr "Kreisbogen" + +#: graphicswin.cpp:133 +msgid "&Bezier Cubic Spline" +msgstr "Kubischer &Bezier-Spline" + +#: graphicswin.cpp:135 +msgid "&Text in TrueType Font" +msgstr "&Text in Truetype-Font" + +#: graphicswin.cpp:136 +msgid "&Image" +msgstr "B&ild" + +#: graphicswin.cpp:138 +msgid "To&ggle Construction" +msgstr "Konstruktionselement an/aus" + +#: graphicswin.cpp:139 +msgid "Tangent &Arc at Point" +msgstr "Bogentangente an Punkt" + +#: graphicswin.cpp:140 +msgid "Split Curves at &Intersection" +msgstr "Kurven im Schnittpunkt trennen" + +#: graphicswin.cpp:142 +msgid "&Constrain" +msgstr "&Einschränkung" + +#: graphicswin.cpp:143 +msgid "&Distance / Diameter" +msgstr "Abstand / Durchmesser" + +#: graphicswin.cpp:144 +msgid "Re&ference Dimension" +msgstr "Referenzangabe" + +#: graphicswin.cpp:145 +msgid "A&ngle" +msgstr "Winkel" + +#: graphicswin.cpp:146 +msgid "Reference An&gle" +msgstr "Referenzwinkel" + +#: graphicswin.cpp:147 +msgid "Other S&upplementary Angle" +msgstr "Komplementärwinkel" + +#: graphicswin.cpp:148 +msgid "Toggle R&eference Dim" +msgstr "Referenzangabe ein/aus" + +#: graphicswin.cpp:150 +msgid "&Horizontal" +msgstr "Horizontal" + +#: graphicswin.cpp:151 +msgid "&Vertical" +msgstr "&Vertikal" + +#: graphicswin.cpp:153 +msgid "&On Point / Curve / Plane" +msgstr "Auf Punkt / Kurve / Ebene" + +#: graphicswin.cpp:154 +msgid "E&qual Length / Radius / Angle" +msgstr "Gleicher Abstand / Radius / Winkel" + +#: graphicswin.cpp:155 +msgid "Length Ra&tio" +msgstr "Längenverhältnis" + +#: graphicswin.cpp:156 +msgid "Length Diff&erence" +msgstr "Längendifferenz" + +#: graphicswin.cpp:157 +msgid "At &Midpoint" +msgstr "Auf &Mittelpunkt" + +#: graphicswin.cpp:158 +msgid "S&ymmetric" +msgstr "Symmetrisch" + +#: graphicswin.cpp:159 +msgid "Para&llel / Tangent" +msgstr "Paral&llel / Tangente" + +#: graphicswin.cpp:160 +msgid "&Perpendicular" +msgstr "Rechtwinklig" + +#: graphicswin.cpp:161 +msgid "Same Orient&ation" +msgstr "Gleiche Orientierung" + +#: graphicswin.cpp:162 +msgid "Lock Point Where &Dragged" +msgstr "Punkt an Position fixieren" + +#: graphicswin.cpp:164 +msgid "Comment" +msgstr "Kommentar" + +#: graphicswin.cpp:166 +msgid "&Analyze" +msgstr "&Analyse" + +#: graphicswin.cpp:167 +msgid "Measure &Volume" +msgstr "&Volumen bestimmen" + +#: graphicswin.cpp:168 +msgid "Measure A&rea" +msgstr "Fläche bestimmen" + +#: graphicswin.cpp:169 +msgid "Measure &Perimeter" +msgstr "Umfang bestimmen" + +#: graphicswin.cpp:170 +msgid "Show &Interfering Parts" +msgstr "Überlagernde Teile anzeigen" + +#: graphicswin.cpp:171 +msgid "Show &Naked Edges" +msgstr "Freiliegende Kanten anzeigen" + +#: graphicswin.cpp:172 +msgid "Show &Center of Mass" +msgstr "Massenmittelpunkt anzeigen" + +#: graphicswin.cpp:174 +msgid "Show &Underconstrained Points" +msgstr "&Unterbeschränkte Punkte anzeigen" + +#: graphicswin.cpp:176 +msgid "&Trace Point" +msgstr "Punkt nachzeichnen" + +#: graphicswin.cpp:177 +msgid "&Stop Tracing..." +msgstr "Nachzeichnen beenden" + +#: graphicswin.cpp:178 +msgid "Step &Dimension..." +msgstr "Schrittgröße…" + +#: graphicswin.cpp:180 +msgid "&Help" +msgstr "&Hilfe" + +#: graphicswin.cpp:181 +msgid "&Language" +msgstr "Sprache" + +#: graphicswin.cpp:182 +msgid "&Website / Manual" +msgstr "&Website / Anleitung" + +#: graphicswin.cpp:184 +msgid "&About" +msgstr "Über" + +#: graphicswin.cpp:352 +msgid "(no recent files)" +msgstr "(keine vorhergehenden Dateien)" + +#: graphicswin.cpp:360 +#, c-format +msgid "File '%s' does not exist." +msgstr "" + +#: graphicswin.cpp:721 +msgid "No workplane is active, so the grid will not appear." +msgstr "" +"Das Raster wird nicht angezeigt, weil keine Arbeitsebene ausgewählt ist." + +#: graphicswin.cpp:730 +msgid "" +"The perspective factor is set to zero, so the view will always be a parallel " +"projection.\n" +"\n" +"For a perspective projection, modify the perspective factor in the " +"configuration screen. A value around 0.3 is typical." +msgstr "" +"Der Perspektivfaktor is auf Null gesetzt, also wird die Ansicht eine " +"Parallelprojektion sein.\n" +"\n" +"Ändern Sie den Faktor für die Perspektivprojektion in der " +"Konfigurationsmaske. Ein typischer Wert ist ca. 0,3." + +#: graphicswin.cpp:809 +msgid "" +"Select a point; this point will become the center of the view on screen." +msgstr "" +"Wählen Sie einen Punkt aus; dieser Punkt wird im Mittelpunkt der " +"Bildschirmansicht sein." + +#: graphicswin.cpp:1103 +msgid "No additional entities share endpoints with the selected entities." +msgstr "" +"Die ausgewählten Objekte teilen keine gemeinsamen Endpunkte mit anderen " +"Objekten." + +#: graphicswin.cpp:1121 +msgid "" +"To use this command, select a point or other entity from an linked part, or " +"make a link group the active group." +msgstr "" +"Für diesen Befehl wählen Sie einen Punkt oder ein anderes Objekt von einem " +"verknüpften Teil aus, oder aktivieren Sie eine verknüpfte Gruppe." + +#: graphicswin.cpp:1144 +msgid "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) " +"to define the plane for the snap grid." +msgstr "" +"Es wurde keine Arbeitsebene ausgewählt. Aktivieren Sie eine Arbeitsebene " +"(mit Skizze -> In Arbeitsebene), um die Ebene für das Gitterraster zu " +"definieren." + +#: graphicswin.cpp:1151 +msgid "" +"Can't snap these items to grid; select points, text comments, or constraints " +"with a label. To snap a line, select its endpoints." +msgstr "" +"Diese Objekte können nicht auf das Raster ausgerichtet werden. Dies geht nur " +"für Punkte, Textkommentare, oder Einschränkungen mit einer Bezeichnung. Um " +"eine Linie auf das Raster auszurichten, wählen Sie deren Endpunkte aus." + +#: graphicswin.cpp:1239 +msgid "No workplane selected. Activating default workplane for this group." +msgstr "" +"Es wurde keine Arbeitsebene ausgewählt. Die Standard-Arbeitsebene für diese " +"Gruppe wird aktiviert." + +#: graphicswin.cpp:1242 +msgid "" +"No workplane is selected, and the active group does not have a default " +"workplane. Try selecting a workplane, or activating a sketch-in-new-" +"workplane group." +msgstr "" +"Es wurde keine Arbeitsebene ausgewählt, und die aktive Gruppe hat keine " +"standardmäßige Arbeitsebene. Wählen Sie eine Arbeitsebene aus, oder " +"erstellen Sie eine Gruppe in einer neuen Arbeitsebene." + +#: graphicswin.cpp:1263 +msgid "" +"Bad selection for tangent arc at point. Select a single point, or select " +"nothing to set up arc parameters." +msgstr "" +"Ungültige Auswahl für Bogentangente an Punkt. Wählen Sie einen einzelnen " +"Punkt. Um die Bogenparameter anzugeben, wählen Sie nichts aus." + +#: graphicswin.cpp:1274 +msgid "click point on arc (draws anti-clockwise)" +msgstr "" +"Erstellen Sie einen Punkt auf dem Bogen (zeichnet im Gegenuhrzeigersinn)" + +#: graphicswin.cpp:1275 +msgid "click to place datum point" +msgstr "Klicken Sie, um einen Bezugspunkt zu platzieren" + +#: graphicswin.cpp:1276 +msgid "click first point of line segment" +msgstr "Klicken Sie auf den ersten Punkt des Liniensegments" + +#: graphicswin.cpp:1278 +msgid "click first point of construction line segment" +msgstr "Klicken Sie auf den ersten Punkt der Konstruktionslinie" + +#: graphicswin.cpp:1279 +msgid "click first point of cubic segment" +msgstr "Klicken Sie auf den ersten Punkt der kubischen Linie" + +#: graphicswin.cpp:1280 +msgid "click center of circle" +msgstr "Klicken Sie auf den Kreismittelpunkt" + +#: graphicswin.cpp:1281 +msgid "click origin of workplane" +msgstr "Klicken Sie auf den Ursprungspunkt der Arbeitsebene" + +#: graphicswin.cpp:1282 +msgid "click one corner of rectangle" +msgstr "Klicken Sie auf eine Ecke des Rechtecks" + +#: graphicswin.cpp:1283 +msgid "click top left of text" +msgstr "Klicken Sie auf die obere linke Ecke des Texts" + +#: graphicswin.cpp:1289 +msgid "click top left of image" +msgstr "Klicken Sie auf die obere linke Ecke des Bilds" + +#: graphicswin.cpp:1301 +msgid "" +"No entities are selected. Select entities before trying to toggle their " +"construction state." +msgstr "" +"Es wurden keine Objekte ausgewählt Wählen Sie Objekte aus, bevor Sie sie von/" +"zu Konstruktionsmerkmalen umwandeln." + +#: group.cpp:86 +msgctxt "group-name" +msgid "sketch-in-3d" +msgstr "Skizze-in-3D" + +#: group.cpp:142 +msgid "" +"Bad selection for new sketch in workplane. This group can be created with:\n" +"\n" +" * a point (through the point, orthogonal to coordinate axes)\n" +" * a point and two line segments (through the point, parallel to the " +"lines)\n" +" * a workplane (copy of the workplane)\n" +msgstr "" +"Ungültige Auswahl für Skizze in neuer Arbeitsebene Diese Gruppe kann " +"erstellt werden mit:\n" +"\n" +" * einem Punkt (durch den Punkt, orthogonal zu den Koordinatenachsen)\n" +" * einem Punkt und zwei Liniensegmenten (durch den Punkt, parallel zu den " +"Linien)\n" +" * einer Arbeitsebene (Kopie der Arbeitsebene)\n" + +#: group.cpp:154 +msgid "" +"Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " +"will be extruded normal to the workplane." +msgstr "" +"Aktivieren Sie vor der Extrusion eine Arbeitsebene (mit Skizze -> In " +"Arbeitsebene). Die Skizze wird senkrecht zur Arbeitsebene extrudiert" + +#: group.cpp:163 +msgctxt "group-name" +msgid "extrude" +msgstr "Extrusion" + +#: group.cpp:168 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:179 +msgid "" +"Bad selection for new lathe group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Ungültige Auswahl für neue Gruppe mit Drehquerschnitt Diese Gruppe kann " +"erstellt werden mit:\n" +"\n" +" * einem Punkt und einem Liniensegment oder einer Normale (Drehung um " +"eine Achse parallel zur Linie/Normalen, durch den Punkt)\n" +" * einem Liniensegment (Drehung um das Liniensegment)\n" + +#: group.cpp:189 +msgctxt "group-name" +msgid "lathe" +msgstr "Drehung" + +#: group.cpp:194 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:205 +msgid "" +"Bad selection for new revolve group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:217 +msgctxt "group-name" +msgid "revolve" +msgstr "" + +#: group.cpp:222 +msgid "Helix operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:233 +msgid "" +"Bad selection for new helix group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:245 +msgctxt "group-name" +msgid "helix" +msgstr "" + +#: group.cpp:258 +msgid "" +"Bad selection for new rotation. This group can be created with:\n" +"\n" +" * a point, while locked in workplane (rotate in plane, about that " +"point)\n" +" * a point and a line or a normal (rotate about an axis through the " +"point, and parallel to line / normal)\n" +msgstr "" +"Ungültige Auswahl für neue Rotation Diese Gruppe kann erstellt werden mit:\n" +"\n" +" * einem Punkt in vorgegebener Arbeitsebene (in der Ebene um den Punkt " +"drehen)\n" +" * einem Punkt und einer Linie oder einer Normale (um eine Achse durch " +"den Punkt drehen, parallel zur Linie / Normale)\n" + +#: group.cpp:271 +msgctxt "group-name" +msgid "rotate" +msgstr "Drehen" + +#: group.cpp:282 +msgctxt "group-name" +msgid "translate" +msgstr "Versetzen" + +#: group.cpp:400 +msgid "(unnamed)" +msgstr "unbenannt" + +#: groupmesh.cpp:708 +msgid "not closed contour, or not all same style!" +msgstr "Kontur nicht geschlossen, oder kein einheitlicher Linientyp!" + +#: groupmesh.cpp:721 +msgid "points not all coplanar!" +msgstr "Punkte sind nicht alle koplanar!" + +#: groupmesh.cpp:723 +msgid "contour is self-intersecting!" +msgstr "Kontur überschneidet sich selbst!" + +#: groupmesh.cpp:725 +msgid "zero-length edge!" +msgstr "Kante mit Länge Null!" + +#: modify.cpp:254 +msgid "Must be sketching in workplane to create tangent arc." +msgstr "Eine Bogentangente kann nur in einer Arbeitsebene erstellt werden." + +#: modify.cpp:301 +msgid "" +"To create a tangent arc, select a point where two non-construction lines or " +"circles in this group and workplane join." +msgstr "" +"Um eine Bogentangente zu erstellen, wählen Sie einen Punkt, in dem sich zwei " +"nicht-Konstruktionslinien oder -kreise in dieser Gruppe und Arbeitsebene " +"treffen. " + +#: modify.cpp:388 +msgid "" +"Couldn't round this corner. Try a smaller radius, or try creating the " +"desired geometry by hand with tangency constraints." +msgstr "" +"Diese Ecke konnte nicht abgerundet werden. Versuchen Sie einen kleineren " +"Radius, oder erstellen Sie die gewünschte Geometrie von Hand mit \"Tangente" +"\"-Einschränkungen." + +#: modify.cpp:597 +msgid "Couldn't split this entity; lines, circles, or cubics only." +msgstr "" +"Dieses Objekt konnte nicht geteilt werden. Dies geht nur für Linien, Kreise " +"oder kubische Splines." + +#: modify.cpp:624 +msgid "Must be sketching in workplane to split." +msgstr "Trennen ist nur in einer Arbeitsebene möglich." + +#: modify.cpp:631 +msgid "" +"Select two entities that intersect each other (e.g. two lines/circles/arcs " +"or a line/circle/arc and a point)." +msgstr "" +"Wählen Sie zwei Objekte aus, die sich schneiden (z.B. zwei Linien/Kreise/" +"Bögen, oder eine Linie/Kreis/Bogen und ein Punkt)." + +#: modify.cpp:736 +msgid "Can't split; no intersection found." +msgstr "Trennen nicht möglich; keine Überschneidung gefunden." + +#: mouse.cpp:560 +msgid "Assign to Style" +msgstr "Linientyp zuordnen" + +#: mouse.cpp:576 +msgid "No Style" +msgstr "Kein Linientyp" + +#: mouse.cpp:579 +msgid "Newly Created Custom Style..." +msgstr "Neu erstellter benutzerdefinierter Linientyp…" + +#: mouse.cpp:586 +msgid "Group Info" +msgstr "Info zu Gruppe" + +#: mouse.cpp:606 +msgid "Style Info" +msgstr "Info zu Linientyp" + +#: mouse.cpp:626 +msgid "Select Edge Chain" +msgstr "Kantenverlauf auswählen" + +#: mouse.cpp:632 +msgid "Toggle Reference Dimension" +msgstr "Von/zu Referenzangabe wechseln" + +#: mouse.cpp:638 +msgid "Other Supplementary Angle" +msgstr "Anderer Komplementärwinkel" + +#: mouse.cpp:643 +msgid "Snap to Grid" +msgstr "Auf Raster ausrichten" + +#: mouse.cpp:652 +msgid "Remove Spline Point" +msgstr "Spline-Punkt löschen" + +#: mouse.cpp:687 +msgid "Add Spline Point" +msgstr "Spline-Punkt hinzufügen" + +#: mouse.cpp:691 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "" +"Spline-Punkt kann nicht hinzugefügt werden: maximale Anzahl der Punkte " +"erreicht." + +#: mouse.cpp:716 +msgid "Toggle Construction" +msgstr "Konstruktionselement an/aus" + +#: mouse.cpp:731 +msgid "Delete Point-Coincident Constraint" +msgstr "Einschränkung \"Punkte deckungsgleich\" löschen" + +#: mouse.cpp:750 +msgid "Cut" +msgstr "Ausschneiden" + +#: mouse.cpp:752 +msgid "Copy" +msgstr "Kopieren" + +#: mouse.cpp:756 +msgid "Select All" +msgstr "Alle auswählen" + +#: mouse.cpp:761 +msgid "Paste" +msgstr "Einfügen" + +#: mouse.cpp:763 +msgid "Paste Transformed..." +msgstr "Einfügen und transformieren…" + +#: mouse.cpp:768 +msgid "Delete" +msgstr "Löschen" + +#: mouse.cpp:771 +msgid "Unselect All" +msgstr "Alle deselektieren" + +#: mouse.cpp:778 +msgid "Unselect Hovered" +msgstr "Aktive deselektieren" + +#: mouse.cpp:787 +msgid "Zoom to Fit" +msgstr "Zoom an Bildschirm anpassen" + +#: mouse.cpp:990 mouse.cpp:1277 +msgid "click next point of line, or press Esc" +msgstr "Klicken Sie auf den nächsten Punkt der Linie, oder drücken Sie Esc" + +#: mouse.cpp:996 +msgid "" +"Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Ein Rechteck kann nicht in 3D erstellt werden. Aktivieren Sie zuerst eine " +"Arbeitsebene mit \"Skizze -> In Arbeitsebene\"." + +#: mouse.cpp:1030 +msgid "click to place other corner of rectangle" +msgstr "Klicken Sie auf die gegenüberliegende Ecke des Rechtecks" + +#: mouse.cpp:1050 +msgid "click to set radius" +msgstr "Klicken Sie, um den Radius festzulegen" + +#: mouse.cpp:1055 +msgid "" +"Can't draw arc in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Ein Kreisbogen kann nicht in 3D erstellt werden. Aktivieren Sie zuerst eine " +"Arbeitsebene mit \"Skizze -> In Arbeitsebene\"." + +#: mouse.cpp:1074 +msgid "click to place point" +msgstr "Klicken Sie, um einen Punkt zu platzieren" + +#: mouse.cpp:1090 +msgid "click next point of cubic, or press Esc" +msgstr "" +"Klicken Sie auf den nächsten Punkt der kubischen Linie, oder drücken Sie Esc" + +#: mouse.cpp:1095 +msgid "" +"Sketching in a workplane already; sketch in 3d before creating new workplane." +msgstr "" +"Eine Arbeitsebene ist bereits aktiv. Skizzieren Sie in 3D, bevor Sie eine " +"neue Arbeitsebene erstellen." + +#: mouse.cpp:1111 +msgid "" +"Can't draw text in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Text kann nicht in 3D erstellt werden. Aktivieren Sie zuerst eine " +"Arbeitsebene mit \"Skizze -> In Arbeitsebene\"." + +#: mouse.cpp:1128 +msgid "click to place bottom right of text" +msgstr "Klicken Sie auf die untere rechte Ecke des Texts" + +#: mouse.cpp:1134 +msgid "" +"Can't draw image in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Das Bild kann nicht in 3D erstellt werden. Aktivieren Sie zuerst eine " +"Arbeitsebene mit \"Skizze -> In Arbeitsebene\"." + +#: mouse.cpp:1161 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "NEUER KOMMENTAR -- DOPPELKLICKEN ZUM BEARBEITEN" + +#: platform/gui.cpp:85 platform/gui.cpp:89 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "SolveSpace-Modelle" + +#: platform/gui.cpp:90 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "" + +#: platform/gui.cpp:94 +msgctxt "file-type" +msgid "PNG image" +msgstr "PNG-Datei" + +#: platform/gui.cpp:98 +msgctxt "file-type" +msgid "STL mesh" +msgstr "STL-Netz" + +#: platform/gui.cpp:99 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Wavefront OBJ-Netz" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "Three.js-kompatibles Netz, mit Ansicht" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "Three.js-kompatibles Netz, nur Netz" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Q3D Object file" +msgstr "Q3D Objektdatei" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "VRML text file" +msgstr "VRML Textdatei" + +#: platform/gui.cpp:107 platform/gui.cpp:114 platform/gui.cpp:121 +msgctxt "file-type" +msgid "STEP file" +msgstr "STEP-Datei" + +#: platform/gui.cpp:111 +msgctxt "file-type" +msgid "PDF file" +msgstr "PDF-Datei" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "Eingebettetes Postscript" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "Skalierbare Vektorgrafik" + +#: platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "DXF-Datei (AutoCAD 2007)" + +#: platform/gui.cpp:116 +msgctxt "file-type" +msgid "HPGL file" +msgstr "HPGL-Datei" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "G Code" +msgstr "G-Code" + +#: platform/gui.cpp:126 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "AutoCAD DXF- und DWG-Dateien" + +#: platform/gui.cpp:130 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "Werte durch Komma getrennt (CSV)" + +#: platform/guigtk.cpp:1317 platform/guimac.mm:1360 platform/guiwin.cpp:1608 +msgid "untitled" +msgstr "unbenannt" + +#: platform/guigtk.cpp:1328 platform/guigtk.cpp:1361 platform/guimac.mm:1318 +#: platform/guiwin.cpp:1555 +msgctxt "title" +msgid "Save File" +msgstr "Datei speichern" + +#: platform/guigtk.cpp:1329 platform/guigtk.cpp:1362 platform/guimac.mm:1301 +#: platform/guiwin.cpp:1557 +msgctxt "title" +msgid "Open File" +msgstr "Datei öffnen" + +#: platform/guigtk.cpp:1332 platform/guigtk.cpp:1368 +msgctxt "button" +msgid "_Cancel" +msgstr "_Abbrechen" + +#: platform/guigtk.cpp:1333 platform/guigtk.cpp:1366 +msgctxt "button" +msgid "_Save" +msgstr "_Speichern" + +#: platform/guigtk.cpp:1334 platform/guigtk.cpp:1367 +msgctxt "button" +msgid "_Open" +msgstr "" + +#: style.cpp:166 +msgid "" +"Can't assign style to an entity that's derived from another entity; try " +"assigning a style to this entity's parent." +msgstr "" +"Ein Linientyp kann keinem Objekt zugeordnet werden, das von einem anderen " +"Objekt abgeleitet wurde. Versuchen Sie, dem übergeordneten Objekt einen Typ " +"zuzuordnen." + +#: style.cpp:665 +msgid "Style name cannot be empty" +msgstr "Name des Linientyps kann nicht leer sein." + +#: textscreens.cpp:741 +msgid "Can't repeat fewer than 1 time." +msgstr "Nicht weniger als 1 Wiederholung möglich." + +#: textscreens.cpp:745 +msgid "Can't repeat more than 999 times." +msgstr "Nicht mehr als 999 Wiederholungen möglich." + +#: textscreens.cpp:770 +msgid "Group name cannot be empty" +msgstr "Der Name der Gruppe darf nicht leer sein." + +#: textscreens.cpp:813 +msgid "Opacity must be between zero and one." +msgstr "Durchsichtigkeit muss zwischen Null und Eins sein." + +#: textscreens.cpp:848 +msgid "Radius cannot be zero or negative." +msgstr "Radius darf nicht null oder negativ sein." + +#: toolbar.cpp:18 +msgid "Sketch line segment" +msgstr "Liniensegment erstellen" + +#: toolbar.cpp:20 +msgid "Sketch rectangle" +msgstr "Rechteck erstellen" + +#: toolbar.cpp:22 +msgid "Sketch circle" +msgstr "Kreis erstellen" + +#: toolbar.cpp:24 +msgid "Sketch arc of a circle" +msgstr "Kreisbogen erstellen" + +#: toolbar.cpp:26 +msgid "Sketch curves from text in a TrueType font" +msgstr "Kurven aus Text in einem TrueType-Font erzeugen" + +#: toolbar.cpp:28 +msgid "Sketch image from a file" +msgstr "Skizze von einer Bilddatei erstellen" + +#: toolbar.cpp:30 +msgid "Create tangent arc at selected point" +msgstr "Bogentangente im ausgewählten Punkt erstellen" + +#: toolbar.cpp:32 +msgid "Sketch cubic Bezier spline" +msgstr "Kubischen Bezier-Spline erstellen" + +#: toolbar.cpp:34 +msgid "Sketch datum point" +msgstr "Bezugspunkt erstellen" + +#: toolbar.cpp:36 +msgid "Toggle construction" +msgstr "Konstruktionselement an/aus" + +#: toolbar.cpp:38 +msgid "Split lines / curves where they intersect" +msgstr "Linien / Kurven im Schnittpunkt trennen" + +#: toolbar.cpp:42 +msgid "Constrain distance / diameter / length" +msgstr "Abstand / Durchmesser / Länge einschränken" + +#: toolbar.cpp:44 +msgid "Constrain angle" +msgstr "Winkel einschränken" + +#: toolbar.cpp:46 +msgid "Constrain to be horizontal" +msgstr "Horizontale Einschränkung" + +#: toolbar.cpp:48 +msgid "Constrain to be vertical" +msgstr "Vertikale Einschränkung" + +#: toolbar.cpp:50 +msgid "Constrain to be parallel or tangent" +msgstr "Einschränkung auf Parallele oder Tangente" + +#: toolbar.cpp:52 +msgid "Constrain to be perpendicular" +msgstr "Rechtwinklige Einschränkung" + +#: toolbar.cpp:54 +msgid "Constrain point on line / curve / plane / point" +msgstr "Punkt auf Linie / Kurve / Ebene / Punkt einschränken" + +#: toolbar.cpp:56 +msgid "Constrain symmetric" +msgstr "Symmetrische Einschränkung" + +#: toolbar.cpp:58 +msgid "Constrain equal length / radius / angle" +msgstr "Gleiche Länge / Radius / Winkel einschränken" + +#: toolbar.cpp:60 +msgid "Constrain normals in same orientation" +msgstr "Normale auf gleiche Richtung einschränken" + +#: toolbar.cpp:62 +msgid "Other supplementary angle" +msgstr "Anderer Komplementärwinkel" + +#: toolbar.cpp:64 +msgid "Toggle reference dimension" +msgstr "Von/zu Referenzangabe wechseln" + +#: toolbar.cpp:68 +msgid "New group extruding active sketch" +msgstr "Neue Gruppe mit extrudierter aktiver Skizze" + +#: toolbar.cpp:70 +msgid "New group rotating active sketch" +msgstr "Neue Gruppe mit rotierter aktiver Skizze" + +#: toolbar.cpp:72 +msgid "New group step and repeat rotating" +msgstr "Neue Gruppe mit kopierter gedrehter Skizze" + +#: toolbar.cpp:74 +msgid "New group step and repeat translating" +msgstr "Neue Gruppe mit kopierter versetzter Skizze" + +#: toolbar.cpp:76 +msgid "New group in new workplane (thru given entities)" +msgstr "" +"Neue Gruppe in neuer Arbeitsebene (definiert durch ausgewählte Objekte)" + +#: toolbar.cpp:78 +msgid "New group in 3d" +msgstr "Neue Gruppe in 3D" + +#: toolbar.cpp:80 +msgid "New group linking / assembling file" +msgstr "Neue Gruppe mit verknüpfter Datei" + +#: toolbar.cpp:84 +msgid "Nearest isometric view" +msgstr "Nächste isometrische Ansicht" + +#: toolbar.cpp:86 +msgid "Align view to active workplane" +msgstr "Ansicht auf Arbeitsebene ausrichten" + +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Fehler" + +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Mitteilung" + +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "&OK" + +#: view.cpp:78 +msgid "Scale cannot be zero or negative." +msgstr "Der Maßstab kann nicht Null oder negativ sein." + +#: view.cpp:90 view.cpp:99 +msgid "Bad format: specify x, y, z" +msgstr "Ungültiges Format: geben Sie x, y, z ein" + +# solvespace.cpp:557 +#~ msgctxt "title" +#~ msgid "(new sketch)" +#~ msgstr "(Neue Skizze)" + +#~ msgctxt "title" +#~ msgid "Property Browser" +#~ msgstr "Attribut-Browser" + +#~ msgid "Specify between 0 and 8 digits after the decimal." +#~ msgstr "Geben Sie 0 bis 8 Ziffern nach dem Dezimalzeichen an." + +#~ msgid "click to place bottom left of text" +#~ msgstr "Klicken Sie auf die untere linke Ecke des Texts" + +#~ msgid "Do you want to save the changes you made to the new sketch?" +#~ msgstr "Möchten Sie die Änderungen in Ihrer Skizze speichern?" + +#~ msgid "Your changes will be lost if you don't save them." +#~ msgstr "" +#~ "Ihre Änderungen werden verworfen, wenn sie nicht abgespeichert werden." + +#~ msgctxt "button" +#~ msgid "Save" +#~ msgstr "Speichern" + +#~ msgctxt "button" +#~ msgid "Cancel" +#~ msgstr "Abbrechen" + +#~ msgctxt "button" +#~ msgid "Don't Save" +#~ msgstr "Nicht speichern" + +#~ msgid "An autosave file is available for this project." +#~ msgstr "" +#~ "Eine automatisch gespeicherte Datei ist für dieses Projekt vorhanden." + +#~ msgid "Do you want to load the autosave file instead?" +#~ msgstr "Möchten Sie die automatische Speicherdatei stattdessen öffnen?" + +#~ msgctxt "button" +#~ msgid "Load" +#~ msgstr "Öffnen" + +#~ msgctxt "button" +#~ msgid "Don't Load" +#~ msgstr "Nicht öffnen" + +#~ msgid "" +#~ "Do you want to locate it manually?\n" +#~ "If you select “No”, any geometry that depends on the missing file will be " +#~ "removed." +#~ msgstr "" +#~ "Möchten Sie sie selber auswählen?\n" +#~ "Falls Sie \"Nein\" wählen, wird jegliche mit der fehlenden Datei " +#~ "verknüpfte Geometrie verworfen." + +#~ msgctxt "button" +#~ msgid "Yes" +#~ msgstr "Ja" + +#~ msgctxt "button" +#~ msgid "No" +#~ msgstr "Nein" + +#~ msgctxt "button" +#~ msgid "OK" +#~ msgstr "OK" + +#~ msgid "_Cancel" +#~ msgstr "_Abbrechen" + +#~ msgid "_Open" +#~ msgstr "_Öffnen" + +#~ msgid "" +#~ "The file has changed since it was last saved.\n" +#~ "\n" +#~ "Do you want to save the changes?" +#~ msgstr "" +#~ "Die Datei wurde seit der letzten Speicherung geändert.\n" +#~ "\n" +#~ "Möchten Sie die Änderungen speichern?" + +#~ msgctxt "title" +#~ msgid "Modified File" +#~ msgstr "Geänderte Datei" + +#~ msgctxt "button" +#~ msgid "Do_n't Save" +#~ msgstr "Nicht speichern" + +#~ msgid "" +#~ "An autosave file is available for this project.\n" +#~ "\n" +#~ "Do you want to load the autosave file instead?" +#~ msgstr "" +#~ "Eine automatisch gespeicherte Datei ist für dieses Projekt vorhanden.\n" +#~ "\n" +#~ "Wollen Sie die automatisch gespeicherte Datei öffnen?" + +#~ msgctxt "title" +#~ msgid "Autosave Available" +#~ msgstr "Automatische Speicherdatei verfügbar" + +#~ msgctxt "button" +#~ msgid "_Load autosave" +#~ msgstr "AutoDatei öffnen" + +#~ msgctxt "button" +#~ msgid "Do_n't Load" +#~ msgstr "Nicht öffnen" + +#~ msgctxt "button" +#~ msgid "_Yes" +#~ msgstr "_Ja" + +#~ msgctxt "button" +#~ msgid "_No" +#~ msgstr "_Nein" diff --git a/res/locales/en_US.po b/res/locales/en_US.po new file mode 100644 index 0000000..485950f --- /dev/null +++ b/res/locales/en_US.po @@ -0,0 +1,1877 @@ +# English translations for SolveSpace package. +# Copyright (C) 2017 the SolveSpace authors +# This file is distributed under the same license as the SolveSpace package. +# Automatically generated, 2017. +# +msgid "" +msgstr "" +"Project-Id-Version: SolveSpace 3.0\n" +"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" +"POT-Creation-Date: 2020-11-17 20:50-0500\n" +"PO-Revision-Date: 2017-01-05 10:30+0000\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: clipboard.cpp:274 +msgid "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." +msgstr "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." + +#: clipboard.cpp:291 +msgid "Clipboard is empty; nothing to paste." +msgstr "Clipboard is empty; nothing to paste." + +#: clipboard.cpp:338 +msgid "Number of copies to paste must be at least one." +msgstr "Number of copies to paste must be at least one." + +#: clipboard.cpp:354 textscreens.cpp:783 +msgid "Scale cannot be zero." +msgstr "Scale cannot be zero." + +#: clipboard.cpp:396 +msgid "Select one point to define origin of rotation." +msgstr "Select one point to define origin of rotation." + +#: clipboard.cpp:408 +msgid "Select two points to define translation vector." +msgstr "Select two points to define translation vector." + +#: clipboard.cpp:418 +msgid "" +"Transformation is identity. So all copies will be exactly on top of each " +"other." +msgstr "" +"Transformation is identity. So all copies will be exactly on top of each " +"other." + +#: clipboard.cpp:422 +msgid "Too many items to paste; split this into smaller pastes." +msgstr "Too many items to paste; split this into smaller pastes." + +#: clipboard.cpp:427 +msgid "No workplane active." +msgstr "No workplane active." + +#: confscreen.cpp:410 +msgid "Bad format: specify coordinates as x, y, z" +msgstr "Bad format: specify coordinates as x, y, z" + +#: confscreen.cpp:420 style.cpp:659 textscreens.cpp:805 +msgid "Bad format: specify color as r, g, b" +msgstr "Bad format: specify color as r, g, b" + +#: confscreen.cpp:446 +msgid "" +"The perspective factor will have no effect until you enable View -> Use " +"Perspective Projection." +msgstr "" +"The perspective factor will have no effect until you enable View -> Use " +"Perspective Projection." + +#: confscreen.cpp:459 confscreen.cpp:469 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "Specify between 0 and %d digits after the decimal." + +#: confscreen.cpp:481 +msgid "Export scale must not be zero!" +msgstr "Export scale must not be zero!" + +#: confscreen.cpp:493 +msgid "Cutter radius offset must not be negative!" +msgstr "Cutter radius offset must not be negative!" + +#: confscreen.cpp:547 +msgid "Bad value: autosave interval should be positive" +msgstr "Bad value: autosave interval should be positive" + +#: confscreen.cpp:550 +msgid "Bad format: specify interval in integral minutes" +msgstr "Bad format: specify interval in integral minutes" + +#: constraint.cpp:12 +msgctxt "constr-name" +msgid "pts-coincident" +msgstr "pts-coincident" + +#: constraint.cpp:13 +msgctxt "constr-name" +msgid "pt-pt-distance" +msgstr "pt-pt-distance" + +#: constraint.cpp:14 +msgctxt "constr-name" +msgid "pt-line-distance" +msgstr "pt-line-distance" + +#: constraint.cpp:15 +msgctxt "constr-name" +msgid "pt-plane-distance" +msgstr "pt-plane-distance" + +#: constraint.cpp:16 +msgctxt "constr-name" +msgid "pt-face-distance" +msgstr "pt-face-distance" + +#: constraint.cpp:17 +msgctxt "constr-name" +msgid "proj-pt-pt-distance" +msgstr "proj-pt-pt-distance" + +#: constraint.cpp:18 +msgctxt "constr-name" +msgid "pt-in-plane" +msgstr "pt-in-plane" + +#: constraint.cpp:19 +msgctxt "constr-name" +msgid "pt-on-line" +msgstr "pt-on-line" + +#: constraint.cpp:20 +msgctxt "constr-name" +msgid "pt-on-face" +msgstr "pt-on-face" + +#: constraint.cpp:21 +msgctxt "constr-name" +msgid "eq-length" +msgstr "eq-length" + +#: constraint.cpp:22 +msgctxt "constr-name" +msgid "eq-length-and-pt-ln-dist" +msgstr "eq-length-and-pt-ln-dist" + +#: constraint.cpp:23 +msgctxt "constr-name" +msgid "eq-pt-line-distances" +msgstr "eq-pt-line-distances" + +#: constraint.cpp:24 +msgctxt "constr-name" +msgid "length-ratio" +msgstr "length-ratio" + +#: constraint.cpp:25 +msgctxt "constr-name" +msgid "length-difference" +msgstr "length-difference" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "symmetric" +msgstr "symmetric" + +#: constraint.cpp:27 +msgctxt "constr-name" +msgid "symmetric-h" +msgstr "symmetric-h" + +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "symmetric-v" +msgstr "symmetric-v" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "symmetric-line" +msgstr "symmetric-line" + +#: constraint.cpp:30 +msgctxt "constr-name" +msgid "at-midpoint" +msgstr "at-midpoint" + +#: constraint.cpp:31 +msgctxt "constr-name" +msgid "horizontal" +msgstr "horizontal" + +#: constraint.cpp:32 +msgctxt "constr-name" +msgid "vertical" +msgstr "vertical" + +#: constraint.cpp:33 +msgctxt "constr-name" +msgid "diameter" +msgstr "diameter" + +#: constraint.cpp:34 +msgctxt "constr-name" +msgid "pt-on-circle" +msgstr "pt-on-circle" + +#: constraint.cpp:35 +msgctxt "constr-name" +msgid "same-orientation" +msgstr "same-orientation" + +#: constraint.cpp:36 +msgctxt "constr-name" +msgid "angle" +msgstr "angle" + +#: constraint.cpp:37 +msgctxt "constr-name" +msgid "parallel" +msgstr "parallel" + +#: constraint.cpp:38 +msgctxt "constr-name" +msgid "arc-line-tangent" +msgstr "arc-line-tangent" + +#: constraint.cpp:39 +msgctxt "constr-name" +msgid "cubic-line-tangent" +msgstr "cubic-line-tangent" + +#: constraint.cpp:40 +msgctxt "constr-name" +msgid "curve-curve-tangent" +msgstr "curve-curve-tangent" + +#: constraint.cpp:41 +msgctxt "constr-name" +msgid "perpendicular" +msgstr "perpendicular" + +#: constraint.cpp:42 +msgctxt "constr-name" +msgid "eq-radius" +msgstr "eq-radius" + +#: constraint.cpp:43 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "eq-angle" + +#: constraint.cpp:44 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "eq-line-len-arc-len" + +#: constraint.cpp:45 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "lock-where-dragged" + +#: constraint.cpp:46 +msgctxt "constr-name" +msgid "comment" +msgstr "comment" + +#: constraint.cpp:171 +msgid "" +"Bad selection for distance / diameter constraint. This constraint can apply " +"to:\n" +"\n" +" * two points (distance between points)\n" +" * a line segment (length)\n" +" * two points and a line segment or normal (projected distance)\n" +" * a workplane and a point (minimum distance)\n" +" * a line segment and a point (minimum distance)\n" +" * a plane face and a point (minimum distance)\n" +" * a circle or an arc (diameter)\n" +msgstr "" +"Bad selection for distance / diameter constraint. This constraint can apply " +"to:\n" +"\n" +" * two points (distance between points)\n" +" * a line segment (length)\n" +" * two points and a line segment or normal (projected distance)\n" +" * a workplane and a point (minimum distance)\n" +" * a line segment and a point (minimum distance)\n" +" * a plane face and a point (minimum distance)\n" +" * a circle or an arc (diameter)\n" + +#: constraint.cpp:224 +msgid "" +"Bad selection for on point / curve / plane constraint. This constraint can " +"apply to:\n" +"\n" +" * two points (points coincident)\n" +" * a point and a workplane (point in plane)\n" +" * a point and a line segment (point on line)\n" +" * a point and a circle or arc (point on curve)\n" +" * a point and a plane face (point on face)\n" +msgstr "" +"Bad selection for on point / curve / plane constraint. This constraint can " +"apply to:\n" +"\n" +" * two points (points coincident)\n" +" * a point and a workplane (point in plane)\n" +" * a point and a line segment (point on line)\n" +" * a point and a circle or arc (point on curve)\n" +" * a point and a plane face (point on face)\n" + +#: constraint.cpp:286 +msgid "" +"Bad selection for equal length / radius constraint. This constraint can " +"apply to:\n" +"\n" +" * two line segments (equal length)\n" +" * two line segments and two points (equal point-line distances)\n" +" * a line segment and two points (equal point-line distances)\n" +" * a line segment, and a point and line segment (point-line distance " +"equals length)\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" +" * two circles or arcs (equal radius)\n" +" * a line segment and an arc (line segment length equals arc length)\n" +msgstr "" +"Bad selection for equal length / radius constraint. This constraint can " +"apply to:\n" +"\n" +" * two line segments (equal length)\n" +" * two line segments and two points (equal point-line distances)\n" +" * a line segment and two points (equal point-line distances)\n" +" * a line segment, and a point and line segment (point-line distance " +"equals length)\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" +" * two circles or arcs (equal radius)\n" +" * a line segment and an arc (line segment length equals arc length)\n" + +#: constraint.cpp:325 +msgid "" +"Bad selection for length ratio constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +msgstr "" +"Bad selection for length ratio constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" + +#: constraint.cpp:342 +msgid "" +"Bad selection for length difference constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments\n" +msgstr "" +"Bad selection for length difference constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments\n" + +#: constraint.cpp:368 +msgid "" +"Bad selection for at midpoint constraint. This constraint can apply to:\n" +"\n" +" * a line segment and a point (point at midpoint)\n" +" * a line segment and a workplane (line's midpoint on plane)\n" +msgstr "" +"Bad selection for at midpoint constraint. This constraint can apply to:\n" +"\n" +" * a line segment and a point (point at midpoint)\n" +" * a line segment and a workplane (line's midpoint on plane)\n" + +#: constraint.cpp:426 +msgid "" +"Bad selection for symmetric constraint. This constraint can apply to:\n" +"\n" +" * two points or a line segment (symmetric about workplane's coordinate " +"axis)\n" +" * line segment, and two points or a line segment (symmetric about line " +"segment)\n" +" * workplane, and two points or a line segment (symmetric about " +"workplane)\n" +msgstr "" +"Bad selection for symmetric constraint. This constraint can apply to:\n" +"\n" +" * two points or a line segment (symmetric about workplane's coordinate " +"axis)\n" +" * line segment, and two points or a line segment (symmetric about line " +"segment)\n" +" * workplane, and two points or a line segment (symmetric about " +"workplane)\n" + +#: constraint.cpp:440 +msgid "" +"A workplane must be active when constraining symmetric without an explicit " +"symmetry plane." +msgstr "" +"A workplane must be active when constraining symmetric without an explicit " +"symmetry plane." + +#: constraint.cpp:470 +msgid "" +"Activate a workplane (with Sketch -> In Workplane) before applying a " +"horizontal or vertical constraint." +msgstr "" +"Activate a workplane (with Sketch -> In Workplane) before applying a " +"horizontal or vertical constraint." + +#: constraint.cpp:483 +msgid "" +"Bad selection for horizontal / vertical constraint. This constraint can " +"apply to:\n" +"\n" +" * two points\n" +" * a line segment\n" +msgstr "" +"Bad selection for horizontal / vertical constraint. This constraint can " +"apply to:\n" +"\n" +" * two points\n" +" * a line segment\n" + +#: constraint.cpp:504 +msgid "" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" +"\n" +" * two normals\n" +msgstr "" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" +"\n" +" * two normals\n" + +#: constraint.cpp:554 +msgid "Must select an angle constraint." +msgstr "Must select an angle constraint." + +#: constraint.cpp:567 +msgid "Must select a constraint with associated label." +msgstr "Must select a constraint with associated label." + +#: constraint.cpp:578 +msgid "" +"Bad selection for angle constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"Bad selection for angle constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" + +#: constraint.cpp:635 +msgid "" +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent." +msgstr "" +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent." + +#: constraint.cpp:659 +msgid "" +"The tangent cubic and line segment must share an endpoint. Constrain them " +"with Constrain -> On Point before constraining tangent." +msgstr "" +"The tangent cubic and line segment must share an endpoint. Constrain them " +"with Constrain -> On Point before constraining tangent." + +#: constraint.cpp:669 +msgid "Curve-curve tangency must apply in workplane." +msgstr "Curve-curve tangency must apply in workplane." + +#: constraint.cpp:687 +msgid "" +"The curves must share an endpoint. Constrain them with Constrain -> On Point " +"before constraining tangent." +msgstr "" +"The curves must share an endpoint. Constrain them with Constrain -> On Point " +"before constraining tangent." + +#: constraint.cpp:696 +msgid "" +"Bad selection for parallel / tangent constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments (parallel)\n" +" * a line segment and a normal (parallel)\n" +" * two normals (parallel)\n" +" * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +msgstr "" +"Bad selection for parallel / tangent constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments (parallel)\n" +" * a line segment and a normal (parallel)\n" +" * two normals (parallel)\n" +" * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" + +#: constraint.cpp:714 +msgid "" +"Bad selection for perpendicular constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"Bad selection for perpendicular constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" + +#: constraint.cpp:729 +msgid "" +"Bad selection for lock point where dragged constraint. This constraint can " +"apply to:\n" +"\n" +" * a point\n" +msgstr "" +"Bad selection for lock point where dragged constraint. This constraint can " +"apply to:\n" +"\n" +" * a point\n" + +#: constraint.cpp:740 +msgid "click center of comment text" +msgstr "click center of comment text" + +#: export.cpp:19 +msgid "" +"No solid model present; draw one with extrudes and revolves, or use Export " +"2d View to export bare lines and curves." +msgstr "" +"No solid model present; draw one with extrudes and revolves, or use Export " +"2d View to export bare lines and curves." + +#: export.cpp:61 +msgid "" +"Bad selection for export section. Please select:\n" +"\n" +" * nothing, with an active workplane (workplane is section plane)\n" +" * a face (section plane through face)\n" +" * a point and two line segments (plane through point and parallel to " +"lines)\n" +msgstr "" +"Bad selection for export section. Please select:\n" +"\n" +" * nothing, with an active workplane (workplane is section plane)\n" +" * a face (section plane through face)\n" +" * a point and two line segments (plane through point and parallel to " +"lines)\n" + +#: export.cpp:822 +msgid "Active group mesh is empty; nothing to export." +msgstr "Active group mesh is empty; nothing to export." + +#: exportvector.cpp:337 +msgid "freehand lines were replaced with continuous lines" +msgstr "freehand lines were replaced with continuous lines" + +#: exportvector.cpp:339 +msgid "zigzag lines were replaced with continuous lines" +msgstr "zigzag lines were replaced with continuous lines" + +#: exportvector.cpp:593 +msgid "" +"Some aspects of the drawing have no DXF equivalent and were not exported:\n" +msgstr "" +"Some aspects of the drawing have no DXF equivalent and were not exported:\n" + +#: exportvector.cpp:839 +msgid "" +"PDF page size exceeds 200 by 200 inches; many viewers may reject this file." +msgstr "" +"PDF page size exceeds 200 by 200 inches; many viewers may reject this file." + +#: file.cpp:44 group.cpp:91 +msgctxt "group-name" +msgid "sketch-in-plane" +msgstr "sketch-in-plane" + +#: file.cpp:62 +msgctxt "group-name" +msgid "#references" +msgstr "#references" + +#: file.cpp:549 +msgid "" +"Unrecognized data in file. This file may be corrupt, or from a newer version " +"of the program." +msgstr "" +"Unrecognized data in file. This file may be corrupt, or from a newer version " +"of the program." + +#: file.cpp:859 +msgctxt "title" +msgid "Missing File" +msgstr "Missing File" + +#: file.cpp:860 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "The linked file “%s” is not present." + +#: file.cpp:862 +msgctxt "dialog" +msgid "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be " +"permanently removed." +msgstr "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be " +"permanently removed." + +#: file.cpp:865 +msgctxt "button" +msgid "&Yes" +msgstr "&Yes" + +#: file.cpp:867 +msgctxt "button" +msgid "&No" +msgstr "&No" + +#: file.cpp:869 +msgctxt "button" +msgid "&Cancel" +msgstr "&Cancel" + +#: graphicswin.cpp:41 +msgid "&File" +msgstr "&File" + +#: graphicswin.cpp:42 +msgid "&New" +msgstr "&New" + +#: graphicswin.cpp:43 +msgid "&Open..." +msgstr "&Open..." + +#: graphicswin.cpp:44 +msgid "Open &Recent" +msgstr "Open &Recent" + +#: graphicswin.cpp:45 +msgid "&Save" +msgstr "&Save" + +#: graphicswin.cpp:46 +msgid "Save &As..." +msgstr "Save &As..." + +#: graphicswin.cpp:48 +msgid "Export &Image..." +msgstr "Export &Image..." + +#: graphicswin.cpp:49 +msgid "Export 2d &View..." +msgstr "Export 2d &View..." + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "Export 2d &Section..." + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "Export 3d &Wireframe..." + +#: graphicswin.cpp:52 +msgid "Export Triangle &Mesh..." +msgstr "Export Triangle &Mesh..." + +#: graphicswin.cpp:53 +msgid "Export &Surfaces..." +msgstr "Export &Surfaces..." + +#: graphicswin.cpp:54 +msgid "Im&port..." +msgstr "Im&port..." + +#: graphicswin.cpp:57 +msgid "E&xit" +msgstr "E&xit" + +#: graphicswin.cpp:60 +msgid "&Edit" +msgstr "&Edit" + +#: graphicswin.cpp:61 +msgid "&Undo" +msgstr "&Undo" + +#: graphicswin.cpp:62 +msgid "&Redo" +msgstr "&Redo" + +#: graphicswin.cpp:63 +msgid "Re&generate All" +msgstr "Re&generate All" + +#: graphicswin.cpp:65 +msgid "Snap Selection to &Grid" +msgstr "Snap Selection to &Grid" + +#: graphicswin.cpp:66 +msgid "Rotate Imported &90°" +msgstr "Rotate Imported &90°" + +#: graphicswin.cpp:68 +msgid "Cu&t" +msgstr "Cu&t" + +#: graphicswin.cpp:69 +msgid "&Copy" +msgstr "&Copy" + +#: graphicswin.cpp:70 +msgid "&Paste" +msgstr "&Paste" + +#: graphicswin.cpp:71 +msgid "Paste &Transformed..." +msgstr "Paste &Transformed..." + +#: graphicswin.cpp:72 +msgid "&Delete" +msgstr "&Delete" + +#: graphicswin.cpp:74 +msgid "Select &Edge Chain" +msgstr "Select &Edge Chain" + +#: graphicswin.cpp:75 +msgid "Select &All" +msgstr "Select &All" + +#: graphicswin.cpp:76 +msgid "&Unselect All" +msgstr "&Unselect All" + +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "&Line Styles..." + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "&View Projection..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "Con&figuration..." + +#: graphicswin.cpp:84 +msgid "&View" +msgstr "&View" + +#: graphicswin.cpp:85 +msgid "Zoom &In" +msgstr "Zoom &In" + +#: graphicswin.cpp:86 +msgid "Zoom &Out" +msgstr "Zoom &Out" + +#: graphicswin.cpp:87 +msgid "Zoom To &Fit" +msgstr "Zoom To &Fit" + +#: graphicswin.cpp:89 +msgid "Align View to &Workplane" +msgstr "Align View to &Workplane" + +#: graphicswin.cpp:90 +msgid "Nearest &Ortho View" +msgstr "Nearest &Ortho View" + +#: graphicswin.cpp:91 +msgid "Nearest &Isometric View" +msgstr "Nearest &Isometric View" + +#: graphicswin.cpp:92 +msgid "&Center View At Point" +msgstr "&Center View At Point" + +#: graphicswin.cpp:94 +msgid "Show Snap &Grid" +msgstr "Show Snap &Grid" + +#: graphicswin.cpp:95 +msgid "Use &Perspective Projection" +msgstr "Use &Perspective Projection" + +#: graphicswin.cpp:96 +msgid "Dimension &Units" +msgstr "Dimension &Units" + +#: graphicswin.cpp:97 +msgid "Dimensions in &Millimeters" +msgstr "Dimensions in &Millimeters" + +#: graphicswin.cpp:98 +msgid "Dimensions in M&eters" +msgstr "Dimensions in M&eters" + +#: graphicswin.cpp:99 +msgid "Dimensions in &Inches" +msgstr "Dimensions in &Inches" + +#: graphicswin.cpp:101 +msgid "Show &Toolbar" +msgstr "Show &Toolbar" + +#: graphicswin.cpp:102 +msgid "Show Property Bro&wser" +msgstr "Show Property Bro&wser" + +#: graphicswin.cpp:104 +msgid "&Full Screen" +msgstr "&Full Screen" + +#: graphicswin.cpp:106 +msgid "&New Group" +msgstr "&New Group" + +#: graphicswin.cpp:107 +msgid "Sketch In &3d" +msgstr "Sketch In &3d" + +#: graphicswin.cpp:108 +msgid "Sketch In New &Workplane" +msgstr "Sketch In New &Workplane" + +#: graphicswin.cpp:110 +msgid "Step &Translating" +msgstr "Step &Translating" + +#: graphicswin.cpp:111 +msgid "Step &Rotating" +msgstr "Step &Rotating" + +#: graphicswin.cpp:113 +msgid "E&xtrude" +msgstr "E&xtrude" + +#: graphicswin.cpp:114 +msgid "&Helix" +msgstr "&Helix" + +#: graphicswin.cpp:115 +msgid "&Lathe" +msgstr "&Lathe" + +#: graphicswin.cpp:116 +msgid "Re&volve" +msgstr "Re&volve" + +#: graphicswin.cpp:118 +msgid "Link / Assemble..." +msgstr "Link / Assemble..." + +#: graphicswin.cpp:119 +msgid "Link Recent" +msgstr "Link Recent" + +#: graphicswin.cpp:121 +msgid "&Sketch" +msgstr "&Sketch" + +#: graphicswin.cpp:122 +msgid "In &Workplane" +msgstr "In &Workplane" + +#: graphicswin.cpp:123 +msgid "Anywhere In &3d" +msgstr "Anywhere In &3d" + +#: graphicswin.cpp:125 +msgid "Datum &Point" +msgstr "Datum &Point" + +#: graphicswin.cpp:126 +msgid "&Workplane" +msgstr "&Workplane" + +#: graphicswin.cpp:128 +msgid "Line &Segment" +msgstr "Line &Segment" + +#: graphicswin.cpp:129 +msgid "C&onstruction Line Segment" +msgstr "C&onstruction Line Segment" + +#: graphicswin.cpp:130 +msgid "&Rectangle" +msgstr "&Rectangle" + +#: graphicswin.cpp:131 +msgid "&Circle" +msgstr "&Circle" + +#: graphicswin.cpp:132 +msgid "&Arc of a Circle" +msgstr "&Arc of a Circle" + +#: graphicswin.cpp:133 +msgid "&Bezier Cubic Spline" +msgstr "&Bezier Cubic Spline" + +#: graphicswin.cpp:135 +msgid "&Text in TrueType Font" +msgstr "&Text in TrueType Font" + +#: graphicswin.cpp:136 +msgid "&Image" +msgstr "&Image" + +#: graphicswin.cpp:138 +msgid "To&ggle Construction" +msgstr "To&ggle Construction" + +#: graphicswin.cpp:139 +msgid "Tangent &Arc at Point" +msgstr "Tangent &Arc at Point" + +#: graphicswin.cpp:140 +msgid "Split Curves at &Intersection" +msgstr "Split Curves at &Intersection" + +#: graphicswin.cpp:142 +msgid "&Constrain" +msgstr "&Constrain" + +#: graphicswin.cpp:143 +msgid "&Distance / Diameter" +msgstr "&Distance / Diameter" + +#: graphicswin.cpp:144 +msgid "Re&ference Dimension" +msgstr "Re&ference Dimension" + +#: graphicswin.cpp:145 +msgid "A&ngle" +msgstr "A&ngle" + +#: graphicswin.cpp:146 +msgid "Reference An&gle" +msgstr "Reference An&gle" + +#: graphicswin.cpp:147 +msgid "Other S&upplementary Angle" +msgstr "Other S&upplementary Angle" + +#: graphicswin.cpp:148 +msgid "Toggle R&eference Dim" +msgstr "Toggle R&eference Dim" + +#: graphicswin.cpp:150 +msgid "&Horizontal" +msgstr "&Horizontal" + +#: graphicswin.cpp:151 +msgid "&Vertical" +msgstr "&Vertical" + +#: graphicswin.cpp:153 +msgid "&On Point / Curve / Plane" +msgstr "&On Point / Curve / Plane" + +#: graphicswin.cpp:154 +msgid "E&qual Length / Radius / Angle" +msgstr "E&qual Length / Radius / Angle" + +#: graphicswin.cpp:155 +msgid "Length Ra&tio" +msgstr "Length Ra&tio" + +#: graphicswin.cpp:156 +msgid "Length Diff&erence" +msgstr "Length Diff&erence" + +#: graphicswin.cpp:157 +msgid "At &Midpoint" +msgstr "At &Midpoint" + +#: graphicswin.cpp:158 +msgid "S&ymmetric" +msgstr "S&ymmetric" + +#: graphicswin.cpp:159 +msgid "Para&llel / Tangent" +msgstr "Para&llel / Tangent" + +#: graphicswin.cpp:160 +msgid "&Perpendicular" +msgstr "&Perpendicular" + +#: graphicswin.cpp:161 +msgid "Same Orient&ation" +msgstr "Same Orient&ation" + +#: graphicswin.cpp:162 +msgid "Lock Point Where &Dragged" +msgstr "Lock Point Where &Dragged" + +#: graphicswin.cpp:164 +msgid "Comment" +msgstr "Comment" + +#: graphicswin.cpp:166 +msgid "&Analyze" +msgstr "&Analyze" + +#: graphicswin.cpp:167 +msgid "Measure &Volume" +msgstr "Measure &Volume" + +#: graphicswin.cpp:168 +msgid "Measure A&rea" +msgstr "Measure A&rea" + +#: graphicswin.cpp:169 +msgid "Measure &Perimeter" +msgstr "Measure &Perimeter" + +#: graphicswin.cpp:170 +msgid "Show &Interfering Parts" +msgstr "Show &Interfering Parts" + +#: graphicswin.cpp:171 +msgid "Show &Naked Edges" +msgstr "Show &Naked Edges" + +#: graphicswin.cpp:172 +msgid "Show &Center of Mass" +msgstr "Show &Center of Mass" + +#: graphicswin.cpp:174 +msgid "Show &Underconstrained Points" +msgstr "Show &Underconstrained Points" + +#: graphicswin.cpp:176 +msgid "&Trace Point" +msgstr "&Trace Point" + +#: graphicswin.cpp:177 +msgid "&Stop Tracing..." +msgstr "&Stop Tracing..." + +#: graphicswin.cpp:178 +msgid "Step &Dimension..." +msgstr "Step &Dimension..." + +#: graphicswin.cpp:180 +msgid "&Help" +msgstr "&Help" + +#: graphicswin.cpp:181 +msgid "&Language" +msgstr "&Language" + +#: graphicswin.cpp:182 +msgid "&Website / Manual" +msgstr "&Website / Manual" + +#: graphicswin.cpp:184 +msgid "&About" +msgstr "&About" + +#: graphicswin.cpp:352 +msgid "(no recent files)" +msgstr "(no recent files)" + +#: graphicswin.cpp:360 +#, c-format +msgid "File '%s' does not exist." +msgstr "File '%s' does not exist." + +#: graphicswin.cpp:721 +msgid "No workplane is active, so the grid will not appear." +msgstr "No workplane is active, so the grid will not appear." + +#: graphicswin.cpp:730 +msgid "" +"The perspective factor is set to zero, so the view will always be a parallel " +"projection.\n" +"\n" +"For a perspective projection, modify the perspective factor in the " +"configuration screen. A value around 0.3 is typical." +msgstr "" +"The perspective factor is set to zero, so the view will always be a parallel " +"projection.\n" +"\n" +"For a perspective projection, modify the perspective factor in the " +"configuration screen. A value around 0.3 is typical." + +#: graphicswin.cpp:809 +msgid "" +"Select a point; this point will become the center of the view on screen." +msgstr "" +"Select a point; this point will become the center of the view on screen." + +#: graphicswin.cpp:1103 +msgid "No additional entities share endpoints with the selected entities." +msgstr "No additional entities share endpoints with the selected entities." + +#: graphicswin.cpp:1121 +msgid "" +"To use this command, select a point or other entity from an linked part, or " +"make a link group the active group." +msgstr "" +"To use this command, select a point or other entity from an linked part, or " +"make a link group the active group." + +#: graphicswin.cpp:1144 +msgid "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) " +"to define the plane for the snap grid." +msgstr "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) " +"to define the plane for the snap grid." + +#: graphicswin.cpp:1151 +msgid "" +"Can't snap these items to grid; select points, text comments, or constraints " +"with a label. To snap a line, select its endpoints." +msgstr "" +"Can't snap these items to grid; select points, text comments, or constraints " +"with a label. To snap a line, select its endpoints." + +#: graphicswin.cpp:1239 +msgid "No workplane selected. Activating default workplane for this group." +msgstr "No workplane selected. Activating default workplane for this group." + +#: graphicswin.cpp:1242 +msgid "" +"No workplane is selected, and the active group does not have a default " +"workplane. Try selecting a workplane, or activating a sketch-in-new-" +"workplane group." +msgstr "" +"No workplane is selected, and the active group does not have a default " +"workplane. Try selecting a workplane, or activating a sketch-in-new-" +"workplane group." + +#: graphicswin.cpp:1263 +msgid "" +"Bad selection for tangent arc at point. Select a single point, or select " +"nothing to set up arc parameters." +msgstr "" +"Bad selection for tangent arc at point. Select a single point, or select " +"nothing to set up arc parameters." + +#: graphicswin.cpp:1274 +msgid "click point on arc (draws anti-clockwise)" +msgstr "click point on arc (draws anti-clockwise)" + +#: graphicswin.cpp:1275 +msgid "click to place datum point" +msgstr "click to place datum point" + +#: graphicswin.cpp:1276 +msgid "click first point of line segment" +msgstr "click first point of line segment" + +#: graphicswin.cpp:1278 +msgid "click first point of construction line segment" +msgstr "click first point of construction line segment" + +#: graphicswin.cpp:1279 +msgid "click first point of cubic segment" +msgstr "click first point of cubic segment" + +#: graphicswin.cpp:1280 +msgid "click center of circle" +msgstr "click center of circle" + +#: graphicswin.cpp:1281 +msgid "click origin of workplane" +msgstr "click origin of workplane" + +#: graphicswin.cpp:1282 +msgid "click one corner of rectangle" +msgstr "click one corner of rectangle" + +#: graphicswin.cpp:1283 +msgid "click top left of text" +msgstr "click top left of text" + +#: graphicswin.cpp:1289 +msgid "click top left of image" +msgstr "click top left of image" + +#: graphicswin.cpp:1301 +msgid "" +"No entities are selected. Select entities before trying to toggle their " +"construction state." +msgstr "" +"No entities are selected. Select entities before trying to toggle their " +"construction state." + +#: group.cpp:86 +msgctxt "group-name" +msgid "sketch-in-3d" +msgstr "sketch-in-3d" + +#: group.cpp:142 +msgid "" +"Bad selection for new sketch in workplane. This group can be created with:\n" +"\n" +" * a point (through the point, orthogonal to coordinate axes)\n" +" * a point and two line segments (through the point, parallel to the " +"lines)\n" +" * a workplane (copy of the workplane)\n" +msgstr "" +"Bad selection for new sketch in workplane. This group can be created with:\n" +"\n" +" * a point (through the point, orthogonal to coordinate axes)\n" +" * a point and two line segments (through the point, parallel to the " +"lines)\n" +" * a workplane (copy of the workplane)\n" + +#: group.cpp:154 +msgid "" +"Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " +"will be extruded normal to the workplane." +msgstr "" +"Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " +"will be extruded normal to the workplane." + +#: group.cpp:163 +msgctxt "group-name" +msgid "extrude" +msgstr "extrude" + +#: group.cpp:168 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "Lathe operation can only be applied to planar sketches." + +#: group.cpp:179 +msgid "" +"Bad selection for new lathe group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Bad selection for new lathe group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" + +#: group.cpp:189 +msgctxt "group-name" +msgid "lathe" +msgstr "lathe" + +#: group.cpp:194 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "Revolve operation can only be applied to planar sketches." + +#: group.cpp:205 +msgid "" +"Bad selection for new revolve group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Bad selection for new revolve group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" + +#: group.cpp:217 +msgctxt "group-name" +msgid "revolve" +msgstr "revolve" + +#: group.cpp:222 +msgid "Helix operation can only be applied to planar sketches." +msgstr "Helix operation can only be applied to planar sketches." + +#: group.cpp:233 +msgid "" +"Bad selection for new helix group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Bad selection for new helix group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" + +#: group.cpp:245 +msgctxt "group-name" +msgid "helix" +msgstr "helix" + +#: group.cpp:258 +msgid "" +"Bad selection for new rotation. This group can be created with:\n" +"\n" +" * a point, while locked in workplane (rotate in plane, about that " +"point)\n" +" * a point and a line or a normal (rotate about an axis through the " +"point, and parallel to line / normal)\n" +msgstr "" +"Bad selection for new rotation. This group can be created with:\n" +"\n" +" * a point, while locked in workplane (rotate in plane, about that " +"point)\n" +" * a point and a line or a normal (rotate about an axis through the " +"point, and parallel to line / normal)\n" + +#: group.cpp:271 +msgctxt "group-name" +msgid "rotate" +msgstr "rotate" + +#: group.cpp:282 +msgctxt "group-name" +msgid "translate" +msgstr "translate" + +#: group.cpp:400 +msgid "(unnamed)" +msgstr "(unnamed)" + +#: groupmesh.cpp:708 +msgid "not closed contour, or not all same style!" +msgstr "not closed contour, or not all same style!" + +#: groupmesh.cpp:721 +msgid "points not all coplanar!" +msgstr "points not all coplanar!" + +#: groupmesh.cpp:723 +msgid "contour is self-intersecting!" +msgstr "contour is self-intersecting!" + +#: groupmesh.cpp:725 +msgid "zero-length edge!" +msgstr "zero-length edge!" + +#: modify.cpp:254 +msgid "Must be sketching in workplane to create tangent arc." +msgstr "Must be sketching in workplane to create tangent arc." + +#: modify.cpp:301 +msgid "" +"To create a tangent arc, select a point where two non-construction lines or " +"circles in this group and workplane join." +msgstr "" +"To create a tangent arc, select a point where two non-construction lines or " +"circles in this group and workplane join." + +#: modify.cpp:388 +msgid "" +"Couldn't round this corner. Try a smaller radius, or try creating the " +"desired geometry by hand with tangency constraints." +msgstr "" +"Couldn't round this corner. Try a smaller radius, or try creating the " +"desired geometry by hand with tangency constraints." + +#: modify.cpp:597 +msgid "Couldn't split this entity; lines, circles, or cubics only." +msgstr "Couldn't split this entity; lines, circles, or cubics only." + +#: modify.cpp:624 +msgid "Must be sketching in workplane to split." +msgstr "Must be sketching in workplane to split." + +#: modify.cpp:631 +msgid "" +"Select two entities that intersect each other (e.g. two lines/circles/arcs " +"or a line/circle/arc and a point)." +msgstr "" +"Select two entities that intersect each other (e.g. two lines/circles/arcs " +"or a line/circle/arc and a point)." + +#: modify.cpp:736 +msgid "Can't split; no intersection found." +msgstr "Can't split; no intersection found." + +#: mouse.cpp:560 +msgid "Assign to Style" +msgstr "Assign to Style" + +#: mouse.cpp:576 +msgid "No Style" +msgstr "No Style" + +#: mouse.cpp:579 +msgid "Newly Created Custom Style..." +msgstr "Newly Created Custom Style..." + +#: mouse.cpp:586 +msgid "Group Info" +msgstr "Group Info" + +#: mouse.cpp:606 +msgid "Style Info" +msgstr "Style Info" + +#: mouse.cpp:626 +msgid "Select Edge Chain" +msgstr "Select Edge Chain" + +#: mouse.cpp:632 +msgid "Toggle Reference Dimension" +msgstr "Toggle Reference Dimension" + +#: mouse.cpp:638 +msgid "Other Supplementary Angle" +msgstr "Other Supplementary Angle" + +#: mouse.cpp:643 +msgid "Snap to Grid" +msgstr "Snap to Grid" + +#: mouse.cpp:652 +msgid "Remove Spline Point" +msgstr "Remove Spline Point" + +#: mouse.cpp:687 +msgid "Add Spline Point" +msgstr "Add Spline Point" + +#: mouse.cpp:691 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "Cannot add spline point: maximum number of points reached." + +#: mouse.cpp:716 +msgid "Toggle Construction" +msgstr "Toggle Construction" + +#: mouse.cpp:731 +msgid "Delete Point-Coincident Constraint" +msgstr "Delete Point-Coincident Constraint" + +#: mouse.cpp:750 +msgid "Cut" +msgstr "Cut" + +#: mouse.cpp:752 +msgid "Copy" +msgstr "Copy" + +#: mouse.cpp:756 +msgid "Select All" +msgstr "Select All" + +#: mouse.cpp:761 +msgid "Paste" +msgstr "Paste" + +#: mouse.cpp:763 +msgid "Paste Transformed..." +msgstr "Paste Transformed..." + +#: mouse.cpp:768 +msgid "Delete" +msgstr "Delete" + +#: mouse.cpp:771 +msgid "Unselect All" +msgstr "Unselect All" + +#: mouse.cpp:778 +msgid "Unselect Hovered" +msgstr "Unselect Hovered" + +#: mouse.cpp:787 +msgid "Zoom to Fit" +msgstr "Zoom to Fit" + +#: mouse.cpp:990 mouse.cpp:1277 +msgid "click next point of line, or press Esc" +msgstr "click next point of line, or press Esc" + +#: mouse.cpp:996 +msgid "" +"Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " +"Workplane." + +#: mouse.cpp:1030 +msgid "click to place other corner of rectangle" +msgstr "click to place other corner of rectangle" + +#: mouse.cpp:1050 +msgid "click to set radius" +msgstr "click to set radius" + +#: mouse.cpp:1055 +msgid "" +"Can't draw arc in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Can't draw arc in 3d; first, activate a workplane with Sketch -> In " +"Workplane." + +#: mouse.cpp:1074 +msgid "click to place point" +msgstr "click to place point" + +#: mouse.cpp:1090 +msgid "click next point of cubic, or press Esc" +msgstr "click next point of cubic, or press Esc" + +#: mouse.cpp:1095 +msgid "" +"Sketching in a workplane already; sketch in 3d before creating new workplane." +msgstr "" +"Sketching in a workplane already; sketch in 3d before creating new workplane." + +#: mouse.cpp:1111 +msgid "" +"Can't draw text in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Can't draw text in 3d; first, activate a workplane with Sketch -> In " +"Workplane." + +#: mouse.cpp:1128 +msgid "click to place bottom right of text" +msgstr "click to place bottom right of text" + +#: mouse.cpp:1134 +msgid "" +"Can't draw image in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Can't draw image in 3d; first, activate a workplane with Sketch -> In " +"Workplane." + +#: mouse.cpp:1161 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "NEW COMMENT -- DOUBLE-CLICK TO EDIT" + +#: platform/gui.cpp:85 platform/gui.cpp:89 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "SolveSpace models" + +#: platform/gui.cpp:90 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "IDF circuit board" + +#: platform/gui.cpp:94 +msgctxt "file-type" +msgid "PNG image" +msgstr "PNG image" + +#: platform/gui.cpp:98 +msgctxt "file-type" +msgid "STL mesh" +msgstr "STL mesh" + +#: platform/gui.cpp:99 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Wavefront OBJ mesh" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "Three.js-compatible mesh, with viewer" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "Three.js-compatible mesh, mesh only" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Q3D Object file" +msgstr "Q3D Object file" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "VRML text file" +msgstr "VRML text file" + +#: platform/gui.cpp:107 platform/gui.cpp:114 platform/gui.cpp:121 +msgctxt "file-type" +msgid "STEP file" +msgstr "STEP file" + +#: platform/gui.cpp:111 +msgctxt "file-type" +msgid "PDF file" +msgstr "PDF file" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "Encapsulated PostScript" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "Scalable Vector Graphics" + +#: platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "DXF file (AutoCAD 2007)" + +#: platform/gui.cpp:116 +msgctxt "file-type" +msgid "HPGL file" +msgstr "HPGL file" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "G Code" +msgstr "G Code" + +#: platform/gui.cpp:126 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "AutoCAD DXF and DWG files" + +#: platform/gui.cpp:130 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "Comma-separated values" + +#: platform/guigtk.cpp:1317 platform/guimac.mm:1360 platform/guiwin.cpp:1608 +msgid "untitled" +msgstr "untitled" + +#: platform/guigtk.cpp:1328 platform/guigtk.cpp:1361 platform/guimac.mm:1318 +#: platform/guiwin.cpp:1555 +msgctxt "title" +msgid "Save File" +msgstr "Save File" + +#: platform/guigtk.cpp:1329 platform/guigtk.cpp:1362 platform/guimac.mm:1301 +#: platform/guiwin.cpp:1557 +msgctxt "title" +msgid "Open File" +msgstr "Open File" + +#: platform/guigtk.cpp:1332 platform/guigtk.cpp:1368 +msgctxt "button" +msgid "_Cancel" +msgstr "_Cancel" + +#: platform/guigtk.cpp:1333 platform/guigtk.cpp:1366 +msgctxt "button" +msgid "_Save" +msgstr "_Save" + +#: platform/guigtk.cpp:1334 platform/guigtk.cpp:1367 +msgctxt "button" +msgid "_Open" +msgstr "_Open" + +#: style.cpp:166 +msgid "" +"Can't assign style to an entity that's derived from another entity; try " +"assigning a style to this entity's parent." +msgstr "" +"Can't assign style to an entity that's derived from another entity; try " +"assigning a style to this entity's parent." + +#: style.cpp:665 +msgid "Style name cannot be empty" +msgstr "Style name cannot be empty" + +#: textscreens.cpp:741 +msgid "Can't repeat fewer than 1 time." +msgstr "Can't repeat fewer than 1 time." + +#: textscreens.cpp:745 +msgid "Can't repeat more than 999 times." +msgstr "Can't repeat more than 999 times." + +#: textscreens.cpp:770 +msgid "Group name cannot be empty" +msgstr "Group name cannot be empty" + +#: textscreens.cpp:813 +msgid "Opacity must be between zero and one." +msgstr "Opacity must be between zero and one." + +#: textscreens.cpp:848 +msgid "Radius cannot be zero or negative." +msgstr "Radius cannot be zero or negative." + +#: toolbar.cpp:18 +msgid "Sketch line segment" +msgstr "Sketch line segment" + +#: toolbar.cpp:20 +msgid "Sketch rectangle" +msgstr "Sketch rectangle" + +#: toolbar.cpp:22 +msgid "Sketch circle" +msgstr "Sketch circle" + +#: toolbar.cpp:24 +msgid "Sketch arc of a circle" +msgstr "Sketch arc of a circle" + +#: toolbar.cpp:26 +msgid "Sketch curves from text in a TrueType font" +msgstr "Sketch curves from text in a TrueType font" + +#: toolbar.cpp:28 +msgid "Sketch image from a file" +msgstr "Sketch image from a file" + +#: toolbar.cpp:30 +msgid "Create tangent arc at selected point" +msgstr "Create tangent arc at selected point" + +#: toolbar.cpp:32 +msgid "Sketch cubic Bezier spline" +msgstr "Sketch cubic Bezier spline" + +#: toolbar.cpp:34 +msgid "Sketch datum point" +msgstr "Sketch datum point" + +#: toolbar.cpp:36 +msgid "Toggle construction" +msgstr "Toggle construction" + +#: toolbar.cpp:38 +msgid "Split lines / curves where they intersect" +msgstr "Split lines / curves where they intersect" + +#: toolbar.cpp:42 +msgid "Constrain distance / diameter / length" +msgstr "Constrain distance / diameter / length" + +#: toolbar.cpp:44 +msgid "Constrain angle" +msgstr "Constrain angle" + +#: toolbar.cpp:46 +msgid "Constrain to be horizontal" +msgstr "Constrain to be horizontal" + +#: toolbar.cpp:48 +msgid "Constrain to be vertical" +msgstr "Constrain to be vertical" + +#: toolbar.cpp:50 +msgid "Constrain to be parallel or tangent" +msgstr "Constrain to be parallel or tangent" + +#: toolbar.cpp:52 +msgid "Constrain to be perpendicular" +msgstr "Constrain to be perpendicular" + +#: toolbar.cpp:54 +msgid "Constrain point on line / curve / plane / point" +msgstr "Constrain point on line / curve / plane / point" + +#: toolbar.cpp:56 +msgid "Constrain symmetric" +msgstr "Constrain symmetric" + +#: toolbar.cpp:58 +msgid "Constrain equal length / radius / angle" +msgstr "Constrain equal length / radius / angle" + +#: toolbar.cpp:60 +msgid "Constrain normals in same orientation" +msgstr "Constrain normals in same orientation" + +#: toolbar.cpp:62 +msgid "Other supplementary angle" +msgstr "Other supplementary angle" + +#: toolbar.cpp:64 +msgid "Toggle reference dimension" +msgstr "Toggle reference dimension" + +#: toolbar.cpp:68 +msgid "New group extruding active sketch" +msgstr "New group extruding active sketch" + +#: toolbar.cpp:70 +msgid "New group rotating active sketch" +msgstr "New group rotating active sketch" + +#: toolbar.cpp:72 +msgid "New group step and repeat rotating" +msgstr "New group step and repeat rotating" + +#: toolbar.cpp:74 +msgid "New group step and repeat translating" +msgstr "New group step and repeat translating" + +#: toolbar.cpp:76 +msgid "New group in new workplane (thru given entities)" +msgstr "New group in new workplane (thru given entities)" + +#: toolbar.cpp:78 +msgid "New group in 3d" +msgstr "New group in 3d" + +#: toolbar.cpp:80 +msgid "New group linking / assembling file" +msgstr "New group linking / assembling file" + +#: toolbar.cpp:84 +msgid "Nearest isometric view" +msgstr "Nearest isometric view" + +#: toolbar.cpp:86 +msgid "Align view to active workplane" +msgstr "Align view to active workplane" + +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Error" + +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Message" + +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "&OK" + +#: view.cpp:78 +msgid "Scale cannot be zero or negative." +msgstr "Scale cannot be zero or negative." + +#: view.cpp:90 view.cpp:99 +msgid "Bad format: specify x, y, z" +msgstr "Bad format: specify x, y, z" + +#~ msgctxt "title" +#~ msgid "(new sketch)" +#~ msgstr "(new sketch)" + +#~ msgctxt "title" +#~ msgid "Property Browser" +#~ msgstr "Property Browser" diff --git a/res/locales/fr_FR.po b/res/locales/fr_FR.po new file mode 100644 index 0000000..8bea1a9 --- /dev/null +++ b/res/locales/fr_FR.po @@ -0,0 +1,2041 @@ +# French translations for the SolveSpace package. +# Copyright (C) 2018 the SolveSpace authors +# This file is distributed under the same license as the SolveSpace package. +# whitequark , 2018. #zanata +msgid "" +msgstr "" +"Project-Id-Version: SolveSpace 3.0\n" +"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" +"POT-Creation-Date: 2020-11-17 20:50-0500\n" +"PO-Revision-Date: 2018-07-14 06:12+0000\n" +"Last-Translator: whitequark \n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Zanata 4.4.5\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: clipboard.cpp:274 +msgid "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." +msgstr "" +"Couper, coller et copier uniquement dans un plan de travail.\n" +"\n" +"Activez un plan avec \"Dessin -> Dans plan de travail\"." + +#: clipboard.cpp:291 +msgid "Clipboard is empty; nothing to paste." +msgstr "Presse papier vide; rien à coller." + +#: clipboard.cpp:338 +msgid "Number of copies to paste must be at least one." +msgstr "Le nombre de copies à coller doit être d'au moins un." + +#: clipboard.cpp:354 textscreens.cpp:783 +msgid "Scale cannot be zero." +msgstr "L'échelle ne peut pas être zéro." + +#: clipboard.cpp:396 +msgid "Select one point to define origin of rotation." +msgstr "Sélectionnez un point pour définir l'origine de la rotation." + +#: clipboard.cpp:408 +msgid "Select two points to define translation vector." +msgstr "Sélectionnez deux points pour définir le vecteur de translation." + +#: clipboard.cpp:418 +msgid "" +"Transformation is identity. So all copies will be exactly on top of each " +"other." +msgstr "" +"Transformation identique. Donc, toutes les copies seront exactement les unes " +"au-dessus des autres." + +#: clipboard.cpp:422 +msgid "Too many items to paste; split this into smaller pastes." +msgstr "Trop d'éléments à coller; Divisez-les en plus petits groupes." + +#: clipboard.cpp:427 +msgid "No workplane active." +msgstr "Pas d'espace de travail actif." + +#: confscreen.cpp:410 +msgid "Bad format: specify coordinates as x, y, z" +msgstr "Mauvais format: spécifiez les coordonnées comme x, y, z" + +#: confscreen.cpp:420 style.cpp:659 textscreens.cpp:805 +msgid "Bad format: specify color as r, g, b" +msgstr "Mauvais format; spécifiez la couleur comme r, v, b" + +#: confscreen.cpp:446 +msgid "" +"The perspective factor will have no effect until you enable View -> Use " +"Perspective Projection." +msgstr "" +"Le facteur de perspective n'aura aucun effet tant que vous n'aurez pas " +"activé \"Affichage -> Utiliser la projection de perspective\"." + +#: confscreen.cpp:459 confscreen.cpp:469 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "" + +#: confscreen.cpp:481 +msgid "Export scale must not be zero!" +msgstr "L'échelle d'export ne doit pas être zéro!" + +#: confscreen.cpp:493 +msgid "Cutter radius offset must not be negative!" +msgstr "Le décalage du rayon de coupe ne doit pas être négatif!" + +#: confscreen.cpp:547 +msgid "Bad value: autosave interval should be positive" +msgstr "" +"Mauvaise valeur: l'intervalle d'enregistrement automatique devrait être " +"positif" + +#: confscreen.cpp:550 +msgid "Bad format: specify interval in integral minutes" +msgstr "Mauvais format: spécifiez un nombre entier de minutes" + +#: constraint.cpp:12 +msgctxt "constr-name" +msgid "pts-coincident" +msgstr "pts-coïncidence" + +#: constraint.cpp:13 +msgctxt "constr-name" +msgid "pt-pt-distance" +msgstr "pt-pt-distance" + +#: constraint.cpp:14 +msgctxt "constr-name" +msgid "pt-line-distance" +msgstr "pt-ligne-distance" + +#: constraint.cpp:15 +msgctxt "constr-name" +msgid "pt-plane-distance" +msgstr "pt-plan-distance" + +#: constraint.cpp:16 +msgctxt "constr-name" +msgid "pt-face-distance" +msgstr "pt-face-distance" + +#: constraint.cpp:17 +msgctxt "constr-name" +msgid "proj-pt-pt-distance" +msgstr "proj-pt-pt-distance" + +#: constraint.cpp:18 +msgctxt "constr-name" +msgid "pt-in-plane" +msgstr "pt-dans-plan" + +#: constraint.cpp:19 +msgctxt "constr-name" +msgid "pt-on-line" +msgstr "pt-sur-ligne" + +#: constraint.cpp:20 +msgctxt "constr-name" +msgid "pt-on-face" +msgstr "pt-sur-face" + +#: constraint.cpp:21 +msgctxt "constr-name" +msgid "eq-length" +msgstr "eg-longueur" + +#: constraint.cpp:22 +msgctxt "constr-name" +msgid "eq-length-and-pt-ln-dist" +msgstr "eg-longueur-et-pt-dans-dist" + +#: constraint.cpp:23 +msgctxt "constr-name" +msgid "eq-pt-line-distances" +msgstr "eg-pt-ligne-distances" + +#: constraint.cpp:24 +msgctxt "constr-name" +msgid "length-ratio" +msgstr "longueur-ratio" + +#: constraint.cpp:25 +msgctxt "constr-name" +msgid "length-difference" +msgstr "longueur-difference" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "symmetric" +msgstr "symétrique" + +#: constraint.cpp:27 +msgctxt "constr-name" +msgid "symmetric-h" +msgstr "symétrique-h" + +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "symmetric-v" +msgstr "symétrique-v" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "symmetric-line" +msgstr "symétrique-ligne" + +#: constraint.cpp:30 +msgctxt "constr-name" +msgid "at-midpoint" +msgstr "au-point-milieu" + +#: constraint.cpp:31 +msgctxt "constr-name" +msgid "horizontal" +msgstr "horizontal" + +#: constraint.cpp:32 +msgctxt "constr-name" +msgid "vertical" +msgstr "vertical" + +#: constraint.cpp:33 +msgctxt "constr-name" +msgid "diameter" +msgstr "diamètre" + +#: constraint.cpp:34 +msgctxt "constr-name" +msgid "pt-on-circle" +msgstr "pt-sur-cercle" + +#: constraint.cpp:35 +msgctxt "constr-name" +msgid "same-orientation" +msgstr "même-orientation" + +#: constraint.cpp:36 +msgctxt "constr-name" +msgid "angle" +msgstr "angle" + +#: constraint.cpp:37 +msgctxt "constr-name" +msgid "parallel" +msgstr "parallèle" + +#: constraint.cpp:38 +msgctxt "constr-name" +msgid "arc-line-tangent" +msgstr "arc-ligne-tangente" + +#: constraint.cpp:39 +msgctxt "constr-name" +msgid "cubic-line-tangent" +msgstr "cubique-ligne-tangente" + +#: constraint.cpp:40 +msgctxt "constr-name" +msgid "curve-curve-tangent" +msgstr "courbe-courbe-tangente" + +#: constraint.cpp:41 +msgctxt "constr-name" +msgid "perpendicular" +msgstr "perpendiculaire" + +#: constraint.cpp:42 +msgctxt "constr-name" +msgid "eq-radius" +msgstr "eg-rayon" + +#: constraint.cpp:43 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "eg-angle" + +#: constraint.cpp:44 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "eg-ligne-long-arc-long" + +#: constraint.cpp:45 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "verrouillé-où-déplacé" + +#: constraint.cpp:46 +msgctxt "constr-name" +msgid "comment" +msgstr "commentaire" + +#: constraint.cpp:171 +msgid "" +"Bad selection for distance / diameter constraint. This constraint can apply " +"to:\n" +"\n" +" * two points (distance between points)\n" +" * a line segment (length)\n" +" * two points and a line segment or normal (projected distance)\n" +" * a workplane and a point (minimum distance)\n" +" * a line segment and a point (minimum distance)\n" +" * a plane face and a point (minimum distance)\n" +" * a circle or an arc (diameter)\n" +msgstr "" +"Mauvaise sélection pour la contrainte distance / diamètre. Cette contrainte " +"peut s'appliquer à:\n" +"\n" +"    * Deux points (distance entre points)\n" +"    * Un segment de ligne (longueur)\n" +"    * Deux points et un segment de ligne ou normal (distance projetée)\n" +"    * Un plan de travail et un point (distance minimale)\n" +"    * Un segment de ligne et un point (distance minimale)\n" +"    * Une face plane et un point (distance minimale)\n" +"    * Un cercle ou un arc (diamètre)\n" + +#: constraint.cpp:224 +msgid "" +"Bad selection for on point / curve / plane constraint. This constraint can " +"apply to:\n" +"\n" +" * two points (points coincident)\n" +" * a point and a workplane (point in plane)\n" +" * a point and a line segment (point on line)\n" +" * a point and a circle or arc (point on curve)\n" +" * a point and a plane face (point on face)\n" +msgstr "" +"Mauvaise sélection pour la contrainte point / courbe / plan. Cette " +"contrainte peut s'appliquer à:\n" +"\n" +"    * Deux points (points coïncidents)\n" +"    * Un point et un plan de travail (point dans le plan)\n" +"    * Un point et un segment de ligne (point en ligne)\n" +"    * Un point et un cercle ou un arc (point sur courbe)\n" +"    * Un point et une face plane (point sur une face)\n" + +#: constraint.cpp:286 +msgid "" +"Bad selection for equal length / radius constraint. This constraint can " +"apply to:\n" +"\n" +" * two line segments (equal length)\n" +" * two line segments and two points (equal point-line distances)\n" +" * a line segment and two points (equal point-line distances)\n" +" * a line segment, and a point and line segment (point-line distance " +"equals length)\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" +" * two circles or arcs (equal radius)\n" +" * a line segment and an arc (line segment length equals arc length)\n" +msgstr "" +"Mauvaise sélection pour une contrainte de longueur / rayon égale. Cette " +"contrainte peut s'appliquer à:\n" +"\n" +"    * Deux segments de ligne (longueur égale)\n" +"    * Deux segments de ligne et deux points (distances point-ligne égales)\n" +"    * Un segment de ligne et deux points (distances point-ligne égales)\n" +"    * Un segment de ligne ou un segment de ligne et point (distance point-" +"ligne de longueur égale)\n" +"    * Quatre segments de ligne ou des normales (angle entre A, B et C, D " +"égaux)\n" +"    * Trois segments de ligne ou des normales (angle entre A, B et B, C " +"égaux)\n" +"    * Deux cercles ou arcs (rayon égaux)\n" +"    * Un segment de ligne et un arc (la longueur de segment de ligne est " +"égale à la longueur d'arc)\n" + +#: constraint.cpp:325 +msgid "" +"Bad selection for length ratio constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +msgstr "" +"Mauvaise sélection pour la contrainte du rapport de longueur. Cette " +"contrainte peut s'appliquer à:\n" +"\n" +"    * Deux segments de ligne\n" + +#: constraint.cpp:342 +msgid "" +"Bad selection for length difference constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments\n" +msgstr "" +"Mauvaise sélection pour la contrainte de différence de longueur. Cette " +"contrainte peut s'appliquer à:\n" +"\n" +"    * Deux segments de ligne\n" + +#: constraint.cpp:368 +msgid "" +"Bad selection for at midpoint constraint. This constraint can apply to:\n" +"\n" +" * a line segment and a point (point at midpoint)\n" +" * a line segment and a workplane (line's midpoint on plane)\n" +msgstr "" +"Mauvaise sélection pour une contrainte de point médian. Cette contrainte " +"peut s'appliquer à:\n" +"\n" +"    * Un segment de ligne et un point (point au milieu)\n" +"    * Un segment de ligne et un plan de travail (point médian dans le plan)\n" + +#: constraint.cpp:426 +msgid "" +"Bad selection for symmetric constraint. This constraint can apply to:\n" +"\n" +" * two points or a line segment (symmetric about workplane's coordinate " +"axis)\n" +" * line segment, and two points or a line segment (symmetric about line " +"segment)\n" +" * workplane, and two points or a line segment (symmetric about " +"workplane)\n" +msgstr "" +"Mauvaise sélection pour la contrainte symétrique. Cette contrainte peut " +"s'appliquer à:\n" +"\n" +"    * Deux points ou un segment de ligne (symétrique à l'axe des coordonnées " +"du plan de travail)\n" +"    * Segment de ligne, et deux points ou un segment de ligne (symétrique " +"sur le segment de ligne)\n" +"    * Plan de travail, et deux points ou un segment de ligne (symétrique au " +"plan de travail)\n" + +#: constraint.cpp:440 +msgid "" +"A workplane must be active when constraining symmetric without an explicit " +"symmetry plane." +msgstr "" +"Un plan de travail doit être actif lors d'une contrainte de symétrie sans " +"plan de symétrie explicite." + +#: constraint.cpp:470 +msgid "" +"Activate a workplane (with Sketch -> In Workplane) before applying a " +"horizontal or vertical constraint." +msgstr "" +"Activez un plan de travail (avec Dessin -> Dans plan de travail) avant " +"d'appliquer une contrainte horizontale ou verticale." + +#: constraint.cpp:483 +msgid "" +"Bad selection for horizontal / vertical constraint. This constraint can " +"apply to:\n" +"\n" +" * two points\n" +" * a line segment\n" +msgstr "" +"Mauvaise sélection pour la contrainte horizontale / verticale. Cette " +"contrainte peut s'appliquer à:\n" +"\n" +"    * deux points\n" +"    * Un segment de ligne\n" + +#: constraint.cpp:504 +msgid "" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" +"\n" +" * two normals\n" +msgstr "" +"Mauvaise sélection pour la même contrainte d'orientation. Cette contrainte " +"peut s'appliquer à:\n" +"\n" +" * Deux normales\n" + +#: constraint.cpp:554 +msgid "Must select an angle constraint." +msgstr "Vous devez sélectionner une contrainte d'angle." + +#: constraint.cpp:567 +msgid "Must select a constraint with associated label." +msgstr "Vous devez sélectionner une contrainte avec une étiquette associée." + +#: constraint.cpp:578 +msgid "" +"Bad selection for angle constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"Mauvaise sélection pour une contrainte d'angle. Cette contrainte peut " +"s'appliquer à:\n" +"\n" +"    * Deux segments de ligne\n" +"    * Un segment de ligne et une normale\n" +"    * Deux normales\n" + +#: constraint.cpp:635 +msgid "" +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent." +msgstr "" +"L'arc tangent et le segment de ligne doivent partager un point final. " +"Contraignez-les avec \"Contrainte -> Sur point avant de contraindre la " +"tangente\"." + +#: constraint.cpp:659 +msgid "" +"The tangent cubic and line segment must share an endpoint. Constrain them " +"with Constrain -> On Point before constraining tangent." +msgstr "" +"La tangente cubique et le segment de ligne doivent partager un point final. " +"Contraignez-les avec \"Contrainte -> Sur point avant de contraindre la " +"tangente\"." + +#: constraint.cpp:669 +msgid "Curve-curve tangency must apply in workplane." +msgstr "Courbe-Courbe tangence doit s'appliquer dans le plan de travail." + +#: constraint.cpp:687 +msgid "" +"The curves must share an endpoint. Constrain them with Constrain -> On Point " +"before constraining tangent." +msgstr "" +"Les courbes doivent partager un point final. Contraignez-les avec " +"\"Contrainte -> Sur point avant de contraindre la tangente\"." + +#: constraint.cpp:696 +msgid "" +"Bad selection for parallel / tangent constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments (parallel)\n" +" * a line segment and a normal (parallel)\n" +" * two normals (parallel)\n" +" * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +msgstr "" +"Mauvaise sélection pour la contrainte parallèle / tangente. Cette contrainte " +"peut s'appliquer à:\n" +"\n" +"    * Deux segments de ligne (parallèles)\n" +"    * Un segment de ligne et un parallèle (parallèle)\n" +"    * Deux normales (parallèles)\n" +"    * Deux segments de ligne, des arcs ou des Béziers, qui partagent un " +"point final (tangent)\n" + +#: constraint.cpp:714 +msgid "" +"Bad selection for perpendicular constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"Mauvaise sélection pour une contrainte perpendiculaire. Cette contrainte " +"peut s'appliquer à:\n" +"\n" +"    * Deux segments de ligne\n" +"    * Un segment de ligne et une normale\n" +"    * Deux normales\n" + +#: constraint.cpp:729 +msgid "" +"Bad selection for lock point where dragged constraint. This constraint can " +"apply to:\n" +"\n" +" * a point\n" +msgstr "" +"Mauvaise sélection pour le point de verrouillage où la contrainte déplacé. " +"Cette contrainte peut s'appliquer à:\n" +"\n" +"    * un point\n" + +#: constraint.cpp:740 +msgid "click center of comment text" +msgstr "cliquez le centre du texte de commentaire" + +#: export.cpp:19 +msgid "" +"No solid model present; draw one with extrudes and revolves, or use Export " +"2d View to export bare lines and curves." +msgstr "" +"Aucun modèle solide présent; Dessinez-en un avec une extrusion et " +"révolution, ou utilisez \"Exporter vue 2d\" pour exporter les lignes et les " +"courbes dépouillées." + +#: export.cpp:61 +msgid "" +"Bad selection for export section. Please select:\n" +"\n" +" * nothing, with an active workplane (workplane is section plane)\n" +" * a face (section plane through face)\n" +" * a point and two line segments (plane through point and parallel to " +"lines)\n" +msgstr "" +"Mauvaise sélection pour la section export. Sélectionnez:\n" +"\n" +"    * Rien, avec un plan de travail actif (plan de travail est un plan de " +"section)\n" +"    * Une face (plan de coupe au-travers d'une face)\n" +"    * Un point et deux segments de ligne (plan au-travers d'un point et " +"parallèle aux lignes)\n" + +#: export.cpp:822 +msgid "Active group mesh is empty; nothing to export." +msgstr "Le maillage du groupe actif est vide; Rien à exporter." + +#: exportvector.cpp:337 +msgid "freehand lines were replaced with continuous lines" +msgstr "les lignes à main levée ont été remplacées par des lignes continues" + +#: exportvector.cpp:339 +msgid "zigzag lines were replaced with continuous lines" +msgstr "les lignes en zigzag ont été remplacées par des lignes continues" + +#: exportvector.cpp:593 +msgid "" +"Some aspects of the drawing have no DXF equivalent and were not exported:\n" +msgstr "" +"Certains aspects du dessin n'ont pas d'équivalent DXF et n'ont pas été " +"exportés:\n" + +#: exportvector.cpp:839 +msgid "" +"PDF page size exceeds 200 by 200 inches; many viewers may reject this file." +msgstr "" +"La taille de la page PDF dépasse 200 par 200 pouces; De nombreux lecteurs " +"peuvent rejeter ce fichier." + +#: file.cpp:44 group.cpp:91 +msgctxt "group-name" +msgid "sketch-in-plane" +msgstr "dessin-dans-plan" + +#: file.cpp:62 +msgctxt "group-name" +msgid "#references" +msgstr "#références" + +#: file.cpp:549 +msgid "" +"Unrecognized data in file. This file may be corrupt, or from a newer version " +"of the program." +msgstr "" +"Données non reconnues dans le fichier. Ce fichier peut être corrompu ou " +"depuis une version plus récente du programme." + +#: file.cpp:859 +msgctxt "title" +msgid "Missing File" +msgstr "Fichier manquant" + +#: file.cpp:860 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "" + +#: file.cpp:862 +msgctxt "dialog" +msgid "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be " +"permanently removed." +msgstr "" + +#: file.cpp:865 +msgctxt "button" +msgid "&Yes" +msgstr "" + +#: file.cpp:867 +msgctxt "button" +msgid "&No" +msgstr "" + +#: file.cpp:869 +msgctxt "button" +msgid "&Cancel" +msgstr "" + +#: graphicswin.cpp:41 +msgid "&File" +msgstr "&Fichier" + +#: graphicswin.cpp:42 +msgid "&New" +msgstr "&Nouveau" + +#: graphicswin.cpp:43 +msgid "&Open..." +msgstr "&Ouvrir..." + +#: graphicswin.cpp:44 +msgid "Open &Recent" +msgstr "Ouvrir &Récent" + +#: graphicswin.cpp:45 +msgid "&Save" +msgstr "&Sauver" + +#: graphicswin.cpp:46 +msgid "Save &As..." +msgstr "Sauver &Comme..." + +#: graphicswin.cpp:48 +msgid "Export &Image..." +msgstr "Exporter &Image..." + +#: graphicswin.cpp:49 +msgid "Export 2d &View..." +msgstr "Exporter &vue 2D..." + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "Exporter &Section 2d..." + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "Exporter &Fil de fer 3d..." + +#: graphicswin.cpp:52 +msgid "Export Triangle &Mesh..." +msgstr "Exporter Triangle &Maillage..." + +#: graphicswin.cpp:53 +msgid "Export &Surfaces..." +msgstr "Export &Surfaces..." + +#: graphicswin.cpp:54 +msgid "Im&port..." +msgstr "Im&porter..." + +#: graphicswin.cpp:57 +msgid "E&xit" +msgstr "&Quitter" + +#: graphicswin.cpp:60 +msgid "&Edit" +msgstr "&Editer" + +#: graphicswin.cpp:61 +msgid "&Undo" +msgstr "&Annuler" + +#: graphicswin.cpp:62 +msgid "&Redo" +msgstr "&Refaire" + +#: graphicswin.cpp:63 +msgid "Re&generate All" +msgstr "Re&générer Tout" + +#: graphicswin.cpp:65 +msgid "Snap Selection to &Grid" +msgstr "Accrocher la sélection à la &Grille" + +#: graphicswin.cpp:66 +msgid "Rotate Imported &90°" +msgstr "Rotation importation &90°" + +#: graphicswin.cpp:68 +msgid "Cu&t" +msgstr "Co&uper" + +#: graphicswin.cpp:69 +msgid "&Copy" +msgstr "&Copier" + +#: graphicswin.cpp:70 +msgid "&Paste" +msgstr "Co&ller" + +#: graphicswin.cpp:71 +msgid "Paste &Transformed..." +msgstr "Coller &Transformer..." + +#: graphicswin.cpp:72 +msgid "&Delete" +msgstr "&Effacer" + +#: graphicswin.cpp:74 +msgid "Select &Edge Chain" +msgstr "Sélectionner une Chaîne d'&Arêtes" + +#: graphicswin.cpp:75 +msgid "Select &All" +msgstr "Sélectionner &Tout" + +#: graphicswin.cpp:76 +msgid "&Unselect All" +msgstr "&Désélectionner Tout" + +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "&Ligne styles..." + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "&Affichage Perspective..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "Con&figuration..." + +#: graphicswin.cpp:84 +msgid "&View" +msgstr "&Affichage" + +#: graphicswin.cpp:85 +msgid "Zoom &In" +msgstr "Zoom &Avant" + +#: graphicswin.cpp:86 +msgid "Zoom &Out" +msgstr "Zoom A&rrière" + +#: graphicswin.cpp:87 +msgid "Zoom To &Fit" +msgstr "Zoom A&justé" + +#: graphicswin.cpp:89 +msgid "Align View to &Workplane" +msgstr "Aligner la vue au &Plan de travail" + +#: graphicswin.cpp:90 +msgid "Nearest &Ortho View" +msgstr "Vue &Orthogonale la plus proche" + +#: graphicswin.cpp:91 +msgid "Nearest &Isometric View" +msgstr "Vue &Isométrique la plus proche" + +#: graphicswin.cpp:92 +msgid "&Center View At Point" +msgstr "&Centrer la Vue sur le Point" + +#: graphicswin.cpp:94 +msgid "Show Snap &Grid" +msgstr "Afficher la &grille d'accrochage" + +#: graphicswin.cpp:95 +msgid "Use &Perspective Projection" +msgstr "Utiliser la vue en &Perspective" + +#: graphicswin.cpp:96 +msgid "Dimension &Units" +msgstr "&Unités de dimensions" + +#: graphicswin.cpp:97 +msgid "Dimensions in &Millimeters" +msgstr "Dimensions en &Millimètres" + +#: graphicswin.cpp:98 +msgid "Dimensions in M&eters" +msgstr "Dimensions en &Mètres" + +#: graphicswin.cpp:99 +msgid "Dimensions in &Inches" +msgstr "Dimensions en &Pouces" + +#: graphicswin.cpp:101 +msgid "Show &Toolbar" +msgstr "Affichage &Barre d'outils" + +#: graphicswin.cpp:102 +msgid "Show Property Bro&wser" +msgstr "Affichage du &Navigateur de Propriété" + +#: graphicswin.cpp:104 +msgid "&Full Screen" +msgstr "&Plein Ecran" + +#: graphicswin.cpp:106 +msgid "&New Group" +msgstr "&Nouveau Groupe" + +#: graphicswin.cpp:107 +msgid "Sketch In &3d" +msgstr "Dessin en &3d" + +#: graphicswin.cpp:108 +msgid "Sketch In New &Workplane" +msgstr "Dessin dans un nouveau &Plan de travail" + +#: graphicswin.cpp:110 +msgid "Step &Translating" +msgstr "Espacement &Linéaire" + +#: graphicswin.cpp:111 +msgid "Step &Rotating" +msgstr "Espacement &Circulaire" + +#: graphicswin.cpp:113 +msgid "E&xtrude" +msgstr "E&xtruder" + +#: graphicswin.cpp:114 +msgid "&Helix" +msgstr "&Helix" + +#: graphicswin.cpp:115 +msgid "&Lathe" +msgstr "&Lathe" + +#: graphicswin.cpp:116 +msgid "Re&volve" +msgstr "Ré&volution" + +#: graphicswin.cpp:118 +msgid "Link / Assemble..." +msgstr "Lié / Assembler..." + +#: graphicswin.cpp:119 +msgid "Link Recent" +msgstr "Lié Récent" + +#: graphicswin.cpp:121 +msgid "&Sketch" +msgstr "&Dessin" + +#: graphicswin.cpp:122 +msgid "In &Workplane" +msgstr "Dans le &Plan de travail" + +#: graphicswin.cpp:123 +msgid "Anywhere In &3d" +msgstr "N'importe où dans la &3d" + +#: graphicswin.cpp:125 +msgid "Datum &Point" +msgstr "&Point" + +#: graphicswin.cpp:126 +msgid "&Workplane" +msgstr "&Plan de travail" + +#: graphicswin.cpp:128 +msgid "Line &Segment" +msgstr "Ligne - &Polyligne" + +#: graphicswin.cpp:129 +msgid "C&onstruction Line Segment" +msgstr "Ligne de C&onstruction" + +#: graphicswin.cpp:130 +msgid "&Rectangle" +msgstr "&Rectangle" + +#: graphicswin.cpp:131 +msgid "&Circle" +msgstr "&Cercle" + +#: graphicswin.cpp:132 +msgid "&Arc of a Circle" +msgstr "&Arc de Cercle" + +#: graphicswin.cpp:133 +msgid "&Bezier Cubic Spline" +msgstr "Spline Cubique de &Beziers" + +#: graphicswin.cpp:135 +msgid "&Text in TrueType Font" +msgstr "&Texte en Police TrueType" + +#: graphicswin.cpp:136 +msgid "&Image" +msgstr "&Image" + +#: graphicswin.cpp:138 +msgid "To&ggle Construction" +msgstr "&Basculer en mode \"Construction\"" + +#: graphicswin.cpp:139 +msgid "Tangent &Arc at Point" +msgstr "&Arc Tangent au Point" + +#: graphicswin.cpp:140 +msgid "Split Curves at &Intersection" +msgstr "Diviser les Courbes à l'&Intersection" + +#: graphicswin.cpp:142 +msgid "&Constrain" +msgstr "&Constraintes" + +#: graphicswin.cpp:143 +msgid "&Distance / Diameter" +msgstr "&Distance / Diamètre" + +#: graphicswin.cpp:144 +msgid "Re&ference Dimension" +msgstr "Dimension Maîtresse / Indicative" + +#: graphicswin.cpp:145 +msgid "A&ngle" +msgstr "A&ngle" + +#: graphicswin.cpp:146 +msgid "Reference An&gle" +msgstr "An&gle Maître / Indicatif" + +#: graphicswin.cpp:147 +msgid "Other S&upplementary Angle" +msgstr "Autre angle S&upplémentaire" + +#: graphicswin.cpp:148 +msgid "Toggle R&eference Dim" +msgstr "Basculer cote Maîtresse / cote Indicative" + +#: graphicswin.cpp:150 +msgid "&Horizontal" +msgstr "&Horizontal" + +#: graphicswin.cpp:151 +msgid "&Vertical" +msgstr "&Vertical" + +#: graphicswin.cpp:153 +msgid "&On Point / Curve / Plane" +msgstr "&Sur Point / Courbe / Plan" + +#: graphicswin.cpp:154 +msgid "E&qual Length / Radius / Angle" +msgstr "&Egale Longueur / Rayon / Angle" + +#: graphicswin.cpp:155 +msgid "Length Ra&tio" +msgstr "R&apport de Longueur" + +#: graphicswin.cpp:156 +msgid "Length Diff&erence" +msgstr "D&ifférence de Longueur" + +#: graphicswin.cpp:157 +msgid "At &Midpoint" +msgstr "Au &Milieu" + +#: graphicswin.cpp:158 +msgid "S&ymmetric" +msgstr "&Symétrique" + +#: graphicswin.cpp:159 +msgid "Para&llel / Tangent" +msgstr "Para&llèle / Tangent" + +#: graphicswin.cpp:160 +msgid "&Perpendicular" +msgstr "&Perpendiculaire" + +#: graphicswin.cpp:161 +msgid "Same Orient&ation" +msgstr "Même Orient&ation" + +#: graphicswin.cpp:162 +msgid "Lock Point Where &Dragged" +msgstr "Accrocher le point à l'&Emplacement" + +#: graphicswin.cpp:164 +msgid "Comment" +msgstr "Commentaire" + +#: graphicswin.cpp:166 +msgid "&Analyze" +msgstr "&Analyse" + +#: graphicswin.cpp:167 +msgid "Measure &Volume" +msgstr "Mesure &Volume" + +#: graphicswin.cpp:168 +msgid "Measure A&rea" +msgstr "Mesure &Aire" + +#: graphicswin.cpp:169 +msgid "Measure &Perimeter" +msgstr "Mesure &Périmètre" + +#: graphicswin.cpp:170 +msgid "Show &Interfering Parts" +msgstr "Montrer les Pièces &Interférentes" + +#: graphicswin.cpp:171 +msgid "Show &Naked Edges" +msgstr "Montrer les Arêtes &Nues" + +#: graphicswin.cpp:172 +msgid "Show &Center of Mass" +msgstr "Montrer le &Centre de Gravité" + +#: graphicswin.cpp:174 +msgid "Show &Underconstrained Points" +msgstr "Montrer les &sous-contraintes Points" + +#: graphicswin.cpp:176 +msgid "&Trace Point" +msgstr "&Tracer Point" + +#: graphicswin.cpp:177 +msgid "&Stop Tracing..." +msgstr "&Arrêt Tracé..." + +#: graphicswin.cpp:178 +msgid "Step &Dimension..." +msgstr "Espacement &Dimension..." + +#: graphicswin.cpp:180 +msgid "&Help" +msgstr "&Aide" + +#: graphicswin.cpp:181 +msgid "&Language" +msgstr "&Langue" + +#: graphicswin.cpp:182 +msgid "&Website / Manual" +msgstr "&Site web / Manuel" + +#: graphicswin.cpp:184 +msgid "&About" +msgstr "&A propos" + +#: graphicswin.cpp:352 +msgid "(no recent files)" +msgstr "(pas de fichier récent)" + +#: graphicswin.cpp:360 +#, c-format +msgid "File '%s' does not exist." +msgstr "" + +#: graphicswin.cpp:721 +msgid "No workplane is active, so the grid will not appear." +msgstr "Pas de plan de travail actif, donc la grille ne va pas apparaître." + +#: graphicswin.cpp:730 +msgid "" +"The perspective factor is set to zero, so the view will always be a parallel " +"projection.\n" +"\n" +"For a perspective projection, modify the perspective factor in the " +"configuration screen. A value around 0.3 is typical." +msgstr "" +"Le facteur de perspective est réglé à 0, donc la vue restera une projection " +"parallèle.\n" +"\n" +"Pour une projection en perspective, modifiez le facteur de perspective dans " +"l'écran de configuration. Une valeur d'environ 0,3 est typique." + +#: graphicswin.cpp:809 +msgid "" +"Select a point; this point will become the center of the view on screen." +msgstr "" +"Sélectionnez un point. Ce point deviendra le centre de la vue à l'écran." + +#: graphicswin.cpp:1103 +msgid "No additional entities share endpoints with the selected entities." +msgstr "" +"Aucune entité supplémentaire ne partage des points d'extrémité avec les " +"entités sélectionnées." + +#: graphicswin.cpp:1121 +msgid "" +"To use this command, select a point or other entity from an linked part, or " +"make a link group the active group." +msgstr "" +"Pour utiliser cette commande, sélectionnez un point ou une autre entité à " +"partir d'une pièce liée ou créez un groupe de liens dans le groupe actif." + +#: graphicswin.cpp:1144 +msgid "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) " +"to define the plane for the snap grid." +msgstr "" +"Aucun plan de travail n'est actif. Activez un plan de travail (avec Dessin -" +"> Dans plan de travail) pour définir le plan pour la grille d'accrochage." + +#: graphicswin.cpp:1151 +msgid "" +"Can't snap these items to grid; select points, text comments, or constraints " +"with a label. To snap a line, select its endpoints." +msgstr "" +"Impossible d'accrocher ces éléments à la grille. Sélectionnez des points, " +"des textes de commentaires ou des contraintes avec une étiquette. Pour " +"accrocher une ligne, sélectionnez ses points d'extrémité." + +#: graphicswin.cpp:1239 +msgid "No workplane selected. Activating default workplane for this group." +msgstr "" +"Aucun plan de travail sélectionné. Activation du plan de travail par défaut " +"pour ce groupe." + +#: graphicswin.cpp:1242 +msgid "" +"No workplane is selected, and the active group does not have a default " +"workplane. Try selecting a workplane, or activating a sketch-in-new-" +"workplane group." +msgstr "" +"Aucun plan de travail n'est sélectionné et le groupe actif n'a pas de plan " +"de travail par défaut. Essayez de sélectionner un plan de travail ou " +"d'activer un groupe de \"Dessin dans nouveau plan travail\"." + +#: graphicswin.cpp:1263 +msgid "" +"Bad selection for tangent arc at point. Select a single point, or select " +"nothing to set up arc parameters." +msgstr "" +"Mauvaise sélection pour l'arc tangent au point. Sélectionnez un seul point, " +"ou ne sélectionnez rien pour configurer les paramètres de l'arc." + +#: graphicswin.cpp:1274 +msgid "click point on arc (draws anti-clockwise)" +msgstr "" +"cliquez un point sur l'arc (dessine dans le sens inverse des aiguilles d'une " +"montre)" + +#: graphicswin.cpp:1275 +msgid "click to place datum point" +msgstr "cliquez pour placer un point" + +#: graphicswin.cpp:1276 +msgid "click first point of line segment" +msgstr "cliquez le premier point du segment de ligne" + +#: graphicswin.cpp:1278 +msgid "click first point of construction line segment" +msgstr "cliquez le premier point de la ligne de construction" + +#: graphicswin.cpp:1279 +msgid "click first point of cubic segment" +msgstr "cliquez le premier point du segment cubique" + +#: graphicswin.cpp:1280 +msgid "click center of circle" +msgstr "cliquez pour placer le centre du cercle" + +#: graphicswin.cpp:1281 +msgid "click origin of workplane" +msgstr "cliquez pour placer l'origine du plan de travail" + +#: graphicswin.cpp:1282 +msgid "click one corner of rectangle" +msgstr "cliquez un coin du rectangle" + +#: graphicswin.cpp:1283 +msgid "click top left of text" +msgstr "cliquez le haut à gauche du texte" + +#: graphicswin.cpp:1289 +msgid "click top left of image" +msgstr "cliquez le haut à gauche de l'image" + +#: graphicswin.cpp:1301 +msgid "" +"No entities are selected. Select entities before trying to toggle their " +"construction state." +msgstr "" +"Aucune entité n'est sélectionnée. Sélectionnez les entités avant d'essayer " +"de basculer leurs états de construction." + +#: group.cpp:86 +msgctxt "group-name" +msgid "sketch-in-3d" +msgstr "dessin-en-3d" + +#: group.cpp:142 +msgid "" +"Bad selection for new sketch in workplane. This group can be created with:\n" +"\n" +" * a point (through the point, orthogonal to coordinate axes)\n" +" * a point and two line segments (through the point, parallel to the " +"lines)\n" +" * a workplane (copy of the workplane)\n" +msgstr "" +"Mauvaise sélection pour un nouveau dessin dans le plan de travail. Ce groupe " +"peut être créé avec:\n" +"\n" +"    * Un point (par le point, orthogonal aux axes de coordonnées)\n" +"    * Un point et deux segments de ligne (par le point, parallèle aux " +"lignes)\n" +"    * Un plan de travail (copie du plan de travail)\n" + +#: group.cpp:154 +msgid "" +"Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " +"will be extruded normal to the workplane." +msgstr "" +"Activez un plan de travail (Dessin -> Dans plan de travail) avant " +"l'extrusion. Le croquis sera extrudé normalement au plan de travail." + +#: group.cpp:163 +msgctxt "group-name" +msgid "extrude" +msgstr "extruder" + +#: group.cpp:168 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:179 +msgid "" +"Bad selection for new lathe group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Mauvaise sélection pour un nouveau groupe de révolution. Ce groupe peut être " +"créé avec:\n" +"\n" +"    * Un point et un segment de ligne ou normal (révolution autour d'un axe " +"parallèle à la ligne / point normal, par le point)\n" +"    * Un segment de ligne (révolution sur le segment de ligne)\n" + +#: group.cpp:189 +msgctxt "group-name" +msgid "lathe" +msgstr "révolution" + +#: group.cpp:194 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:205 +msgid "" +"Bad selection for new revolve group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:217 +msgctxt "group-name" +msgid "revolve" +msgstr "" + +#: group.cpp:222 +msgid "Helix operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:233 +msgid "" +"Bad selection for new helix group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:245 +msgctxt "group-name" +msgid "helix" +msgstr "" + +#: group.cpp:258 +msgid "" +"Bad selection for new rotation. This group can be created with:\n" +"\n" +" * a point, while locked in workplane (rotate in plane, about that " +"point)\n" +" * a point and a line or a normal (rotate about an axis through the " +"point, and parallel to line / normal)\n" +msgstr "" +"Mauvaise sélection pour une nouvelle rotation. Ce groupe peut être créé " +"avec:\n" +"\n" +"    * Un point, lorsqu'il est verrouillé dans un plan de travail (rotation " +"dans le plan, autour de ce point)\n" +"    * Un point et une ligne ou une normale (tourner autour d'un axe par le " +"point et parallèle à la ligne / normale)\n" + +#: group.cpp:271 +msgctxt "group-name" +msgid "rotate" +msgstr "rotation" + +#: group.cpp:282 +msgctxt "group-name" +msgid "translate" +msgstr "translation" + +#: group.cpp:400 +msgid "(unnamed)" +msgstr "(sans nom)" + +#: groupmesh.cpp:708 +msgid "not closed contour, or not all same style!" +msgstr "contour non fermé ou tout n'est pas du même style!" + +#: groupmesh.cpp:721 +msgid "points not all coplanar!" +msgstr "les points ne sont pas tous coplanaires!" + +#: groupmesh.cpp:723 +msgid "contour is self-intersecting!" +msgstr "le contour s'entrecroise!" + +#: groupmesh.cpp:725 +msgid "zero-length edge!" +msgstr "arête de longueur nulle!" + +#: modify.cpp:254 +msgid "Must be sketching in workplane to create tangent arc." +msgstr "Vous devez dessiner dans un plan pour créer un arc tangent." + +#: modify.cpp:301 +msgid "" +"To create a tangent arc, select a point where two non-construction lines or " +"circles in this group and workplane join." +msgstr "" +"Pour créer un arc tangent, sélectionnez un point où deux lignes (pas de " +"construction) ou cercles de ce groupe et de ce plan se joignent." + +#: modify.cpp:388 +msgid "" +"Couldn't round this corner. Try a smaller radius, or try creating the " +"desired geometry by hand with tangency constraints." +msgstr "" +"Impossible d'arrondir ce coin. Essayez un rayon plus petit, ou essayez de " +"créer la géométrie souhaitée à la main avec des contraintes tangentielles." + +#: modify.cpp:597 +msgid "Couldn't split this entity; lines, circles, or cubics only." +msgstr "" +"Impossible de diviser cette entité; Lignes, cercles ou cubiques uniquement." + +#: modify.cpp:624 +msgid "Must be sketching in workplane to split." +msgstr "Vous devez dessiner dans un plan de travail pour diviser." + +#: modify.cpp:631 +msgid "" +"Select two entities that intersect each other (e.g. two lines/circles/arcs " +"or a line/circle/arc and a point)." +msgstr "" + +#: modify.cpp:736 +msgid "Can't split; no intersection found." +msgstr "Impossible de diviser; pas d'intersection trouvée." + +#: mouse.cpp:560 +msgid "Assign to Style" +msgstr "Appliquer au style" + +#: mouse.cpp:576 +msgid "No Style" +msgstr "Pas de style" + +#: mouse.cpp:579 +msgid "Newly Created Custom Style..." +msgstr "Style personnalisé nouvellement créé ..." + +#: mouse.cpp:586 +msgid "Group Info" +msgstr "Info Groupe" + +#: mouse.cpp:606 +msgid "Style Info" +msgstr "Info Style" + +#: mouse.cpp:626 +msgid "Select Edge Chain" +msgstr "Sélection Chaîne d'arêtes" + +#: mouse.cpp:632 +msgid "Toggle Reference Dimension" +msgstr "Basculer cote maîtresse / cote indicative" + +#: mouse.cpp:638 +msgid "Other Supplementary Angle" +msgstr "Autre angle supplémentaire" + +#: mouse.cpp:643 +msgid "Snap to Grid" +msgstr "Accrocher à la grille" + +#: mouse.cpp:652 +msgid "Remove Spline Point" +msgstr "Effacer le point de la Spline" + +#: mouse.cpp:687 +msgid "Add Spline Point" +msgstr "Ajouter un point à la Spline" + +#: mouse.cpp:691 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "" +"Impossible d'ajouter le point spline: nombre maximum de points atteints." + +#: mouse.cpp:716 +msgid "Toggle Construction" +msgstr "Basculer en mode \"construction\"." + +#: mouse.cpp:731 +msgid "Delete Point-Coincident Constraint" +msgstr "Effacer la contraint Point-Coïncident" + +#: mouse.cpp:750 +msgid "Cut" +msgstr "Couper" + +#: mouse.cpp:752 +msgid "Copy" +msgstr "Copier" + +#: mouse.cpp:756 +msgid "Select All" +msgstr "Sélectionner tout" + +#: mouse.cpp:761 +msgid "Paste" +msgstr "Coller" + +#: mouse.cpp:763 +msgid "Paste Transformed..." +msgstr "Coller transformé..." + +#: mouse.cpp:768 +msgid "Delete" +msgstr "Effacer" + +#: mouse.cpp:771 +msgid "Unselect All" +msgstr "Désélectionner tout" + +#: mouse.cpp:778 +msgid "Unselect Hovered" +msgstr "Désélectionner survolé" + +#: mouse.cpp:787 +msgid "Zoom to Fit" +msgstr "Zoom pour ajuster" + +#: mouse.cpp:990 mouse.cpp:1277 +msgid "click next point of line, or press Esc" +msgstr "cliquez pou le prochain point de ligne or appuyez sur Esc" + +#: mouse.cpp:996 +msgid "" +"Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Impossible de dessiner un rectangle en 3d; D'abord, activez un plan de " +"travail avec \"Dessin -> Dans plan de travail\"." + +#: mouse.cpp:1030 +msgid "click to place other corner of rectangle" +msgstr "cliquez pour placer un autre coin de rectangle" + +#: mouse.cpp:1050 +msgid "click to set radius" +msgstr "cliquez pour ajuster le rayon" + +#: mouse.cpp:1055 +msgid "" +"Can't draw arc in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Ne peut pas dessiner l'arc en 3d; D'abord, activez un plan de travail avec " +"\"Dessin -> Dans plan de travail\"." + +#: mouse.cpp:1074 +msgid "click to place point" +msgstr "cliquez pour placer un point" + +#: mouse.cpp:1090 +msgid "click next point of cubic, or press Esc" +msgstr "cliquez le prochain point cubique ou appuyez sur Esc" + +#: mouse.cpp:1095 +msgid "" +"Sketching in a workplane already; sketch in 3d before creating new workplane." +msgstr "" +"Vous dessinez déjà dans un plan de travail; Sélectionner \"Dessiner en 3d\" " +"avant de créer un nouveau plan de travail." + +#: mouse.cpp:1111 +msgid "" +"Can't draw text in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Impossible de dessiner du texte en 3d; D'abord, activer un plan de travail " +"avec \"Dessin -> Dans plan de travail\"." + +#: mouse.cpp:1128 +msgid "click to place bottom right of text" +msgstr "" + +#: mouse.cpp:1134 +msgid "" +"Can't draw image in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Impossible de dessiner l'image en 3d; D'abord, activez un plan de travail " +"avec \"Dessin -> Dans plan de travail\"." + +#: mouse.cpp:1161 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "NOUVEAU COMMENTAIRE - DOUBLE-CLIQUE POUR EDITER" + +#: platform/gui.cpp:85 platform/gui.cpp:89 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "" + +#: platform/gui.cpp:90 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "" + +#: platform/gui.cpp:94 +msgctxt "file-type" +msgid "PNG image" +msgstr "" + +#: platform/gui.cpp:98 +msgctxt "file-type" +msgid "STL mesh" +msgstr "" + +#: platform/gui.cpp:99 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Q3D Object file" +msgstr "" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "VRML text file" +msgstr "" + +#: platform/gui.cpp:107 platform/gui.cpp:114 platform/gui.cpp:121 +msgctxt "file-type" +msgid "STEP file" +msgstr "" + +#: platform/gui.cpp:111 +msgctxt "file-type" +msgid "PDF file" +msgstr "" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "" + +#: platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "" + +#: platform/gui.cpp:116 +msgctxt "file-type" +msgid "HPGL file" +msgstr "" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "G Code" +msgstr "" + +#: platform/gui.cpp:126 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "" + +#: platform/gui.cpp:130 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "" + +#: platform/guigtk.cpp:1317 platform/guimac.mm:1360 platform/guiwin.cpp:1608 +msgid "untitled" +msgstr "sans nom" + +#: platform/guigtk.cpp:1328 platform/guigtk.cpp:1361 platform/guimac.mm:1318 +#: platform/guiwin.cpp:1555 +msgctxt "title" +msgid "Save File" +msgstr "Sauver fichier" + +#: platform/guigtk.cpp:1329 platform/guigtk.cpp:1362 platform/guimac.mm:1301 +#: platform/guiwin.cpp:1557 +msgctxt "title" +msgid "Open File" +msgstr "Ouvrir Fichier" + +#: platform/guigtk.cpp:1332 platform/guigtk.cpp:1368 +msgctxt "button" +msgid "_Cancel" +msgstr "_Annuler" + +#: platform/guigtk.cpp:1333 platform/guigtk.cpp:1366 +msgctxt "button" +msgid "_Save" +msgstr "_Sauver" + +#: platform/guigtk.cpp:1334 platform/guigtk.cpp:1367 +msgctxt "button" +msgid "_Open" +msgstr "" + +#: style.cpp:166 +msgid "" +"Can't assign style to an entity that's derived from another entity; try " +"assigning a style to this entity's parent." +msgstr "" +"Impossible d'attribuer le style à une entité dérivée d'une autre entité; " +"Essayez d'attribuer un style au parent de cette entité." + +#: style.cpp:665 +msgid "Style name cannot be empty" +msgstr "Le nom d'un style ne peut pas être vide" + +#: textscreens.cpp:741 +msgid "Can't repeat fewer than 1 time." +msgstr "Je ne peux pas répéter moins de 1 fois." + +#: textscreens.cpp:745 +msgid "Can't repeat more than 999 times." +msgstr "Je ne peux pas répéter plus de 999 fois." + +#: textscreens.cpp:770 +msgid "Group name cannot be empty" +msgstr "Un nom de groupe ne peut pas être vide" + +#: textscreens.cpp:813 +msgid "Opacity must be between zero and one." +msgstr "L'opacité doit être entre 0 et 1." + +#: textscreens.cpp:848 +msgid "Radius cannot be zero or negative." +msgstr "Le rayon ne peut pas être zéro ou négatif." + +#: toolbar.cpp:18 +msgid "Sketch line segment" +msgstr "Dessin ligne - polyligne" + +#: toolbar.cpp:20 +msgid "Sketch rectangle" +msgstr "Dessin d'un rectangle" + +#: toolbar.cpp:22 +msgid "Sketch circle" +msgstr "Dessin d'un cercle" + +#: toolbar.cpp:24 +msgid "Sketch arc of a circle" +msgstr "Dessin d'un arc de cercle" + +#: toolbar.cpp:26 +msgid "Sketch curves from text in a TrueType font" +msgstr "Dessin de courbes depuis un texte en police TrueType" + +#: toolbar.cpp:28 +msgid "Sketch image from a file" +msgstr "Dessin d'une image depuis un fichier" + +#: toolbar.cpp:30 +msgid "Create tangent arc at selected point" +msgstr "Créer un arc tangent au point sélectionné" + +#: toolbar.cpp:32 +msgid "Sketch cubic Bezier spline" +msgstr "Dessin d'une spline cubique de Bezier" + +#: toolbar.cpp:34 +msgid "Sketch datum point" +msgstr "Dessin d'un point" + +#: toolbar.cpp:36 +msgid "Toggle construction" +msgstr "Basculer en mode \"construction\"" + +#: toolbar.cpp:38 +msgid "Split lines / curves where they intersect" +msgstr "Diviser lignes / courbes où elles se croisent" + +#: toolbar.cpp:42 +msgid "Constrain distance / diameter / length" +msgstr "Contraindre distance / diamètre / longueur" + +#: toolbar.cpp:44 +msgid "Constrain angle" +msgstr "Contraindre angle" + +#: toolbar.cpp:46 +msgid "Constrain to be horizontal" +msgstr "Contraindre à être horizontal" + +#: toolbar.cpp:48 +msgid "Constrain to be vertical" +msgstr "Contraindre à être vertical" + +#: toolbar.cpp:50 +msgid "Constrain to be parallel or tangent" +msgstr "Contraindre à être parallèle ou tangent" + +#: toolbar.cpp:52 +msgid "Constrain to be perpendicular" +msgstr "Contraindre à être perpendiculaire" + +#: toolbar.cpp:54 +msgid "Constrain point on line / curve / plane / point" +msgstr "Contraindre point sur ligne / courbe / plan / point" + +#: toolbar.cpp:56 +msgid "Constrain symmetric" +msgstr "Contrainte symétrique" + +#: toolbar.cpp:58 +msgid "Constrain equal length / radius / angle" +msgstr "Contrainte égale longueur / rayon / angle" + +#: toolbar.cpp:60 +msgid "Constrain normals in same orientation" +msgstr "Contrainte normales dans la même direction" + +#: toolbar.cpp:62 +msgid "Other supplementary angle" +msgstr "Autre angle supplémentaire" + +#: toolbar.cpp:64 +msgid "Toggle reference dimension" +msgstr "Basculer cote maîtresse / cote indicative" + +#: toolbar.cpp:68 +msgid "New group extruding active sketch" +msgstr "Nouveau groupe d'extrusion du dessin actif" + +#: toolbar.cpp:70 +msgid "New group rotating active sketch" +msgstr "Nouveau groupe de révolution du dessin actif" + +#: toolbar.cpp:72 +msgid "New group step and repeat rotating" +msgstr "Nouveau groupe de répétition circulaire" + +#: toolbar.cpp:74 +msgid "New group step and repeat translating" +msgstr "Nouveau groupe de répétition linéaire" + +#: toolbar.cpp:76 +msgid "New group in new workplane (thru given entities)" +msgstr "" +"Nouveau groupe dans un nouveau plan de travail (Par des entités données)" + +#: toolbar.cpp:78 +msgid "New group in 3d" +msgstr "Nouveau groupe en 3d" + +#: toolbar.cpp:80 +msgid "New group linking / assembling file" +msgstr "Nouveau groupe lié / assemblage" + +#: toolbar.cpp:84 +msgid "Nearest isometric view" +msgstr "Vue isométrique la plus proche" + +#: toolbar.cpp:86 +msgid "Align view to active workplane" +msgstr "Aligner la vue sur le plan de travail actif" + +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Erreur" + +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Message" + +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "" + +#: view.cpp:78 +msgid "Scale cannot be zero or negative." +msgstr "L'échelle ne peut pas être zéro ou négative." + +#: view.cpp:90 view.cpp:99 +msgid "Bad format: specify x, y, z" +msgstr "Mauvais format: Spécifiez x, y, z" + +#~ msgctxt "title" +#~ msgid "(new sketch)" +#~ msgstr "(nouveau dessin)" + +#~ msgctxt "title" +#~ msgid "Property Browser" +#~ msgstr "Navigateur de propriété" + +#~ msgid "Specify between 0 and 8 digits after the decimal." +#~ msgstr "Spécifiez entre 0 et 8 chiffres après la virgule." + +#~ msgid "Show Degrees of &Freedom" +#~ msgstr "Montrer les Degrés de &Liberté" + +#~ msgid "click to place bottom left of text" +#~ msgstr "cliquez pour placer le bas gauche du texte" + +#~ msgid "Do you want to save the changes you made to the new sketch?" +#~ msgstr "" +#~ "Voulez-vous enregistrer les modifications que vous avez apportées au " +#~ "nouveau dessin?" + +#~ msgid "Your changes will be lost if you don't save them." +#~ msgstr "Vos modifications seront perdues si vous ne les enregistrez pas." + +#~ msgctxt "button" +#~ msgid "Save" +#~ msgstr "Sauver" + +#~ msgctxt "button" +#~ msgid "Cancel" +#~ msgstr "Annuler" + +#~ msgctxt "button" +#~ msgid "Don't Save" +#~ msgstr "Ne pas sauver" + +#~ msgid "Do you want to load the autosave file instead?" +#~ msgstr "Voulez-vous charger le fichier de sauvegarde à la place?" + +#~ msgctxt "button" +#~ msgid "Load" +#~ msgstr "Charger" + +#~ msgctxt "button" +#~ msgid "Don't Load" +#~ msgstr "Ne pas charger" + +#~ msgid "" +#~ "Do you want to locate it manually?\n" +#~ "If you select “No”, any geometry that depends on the missing file will be " +#~ "removed." +#~ msgstr "" +#~ "Voulez-vous le localiser manuellement?\n" +#~ "Si vous sélectionnez \"Non\", toute géométrie qui dépend du fichier " +#~ "manquant sera supprimée." + +#~ msgctxt "button" +#~ msgid "Yes" +#~ msgstr "Oui" + +#~ msgctxt "button" +#~ msgid "No" +#~ msgstr "Non" + +#~ msgctxt "button" +#~ msgid "OK" +#~ msgstr "Valider" + +#~ msgid "_Cancel" +#~ msgstr "_Annuler" + +#~ msgid "_Open" +#~ msgstr "_Ouvrir" + +#~ msgid "" +#~ "The file has changed since it was last saved.\n" +#~ "\n" +#~ "Do you want to save the changes?" +#~ msgstr "" +#~ "Le fichier a changé depuis sa dernière sauvegarde.\n" +#~ "\n" +#~ "Voulez-vous enregistrer les modifications?" + +#~ msgctxt "title" +#~ msgid "Modified File" +#~ msgstr "Fichier modifié" + +#~ msgctxt "button" +#~ msgid "Do_n't Save" +#~ msgstr "_Ne pas sauver" + +#~ msgctxt "title" +#~ msgid "Autosave Available" +#~ msgstr "Sauvegarde automatique existante" + +#~ msgctxt "button" +#~ msgid "_Load autosave" +#~ msgstr "_Charger la sauvegarde automatique" + +#~ msgctxt "button" +#~ msgid "Do_n't Load" +#~ msgstr "_Ne pas charger" + +#~ msgctxt "button" +#~ msgid "_Yes" +#~ msgstr "_Oui" + +#~ msgctxt "button" +#~ msgid "_No" +#~ msgstr "_Non" + +#~ msgid "SolveSpace models" +#~ msgstr "Modèles SolveSpace" + +#~ msgid "PNG file" +#~ msgstr "Fichier PNG" + +#~ msgid "STL mesh" +#~ msgstr "Maillage STL" + +#~ msgid "Wavefront OBJ mesh" +#~ msgstr "Maillage Wavefront OBJ" + +#~ msgid "Three.js-compatible mesh, with viewer" +#~ msgstr "Maillage Three-js-compatible, avec visualisateur" + +#~ msgid "Three.js-compatible mesh, mesh only" +#~ msgstr "Maillage Three-js-compatible, maillage seul" + +#~ msgid "STEP file" +#~ msgstr "Fichier STEP" + +#~ msgid "PDF file" +#~ msgstr "Fichier PDF" + +#~ msgid "Encapsulated PostScript" +#~ msgstr "Encapsulated PostScript" + +#~ msgid "Scalable Vector Graphics" +#~ msgstr "Scalable Vector Graphics" + +#~ msgid "DXF file (AutoCAD 2007)" +#~ msgstr "Fichier DXF (AutoCAD 2007)" + +#~ msgid "HPGL file" +#~ msgstr "Fichier HPGL" + +#~ msgid "G Code" +#~ msgstr "G Code" + +#~ msgid "AutoCAD DXF and DWG files" +#~ msgstr "Fichiers AutoCAD DXF et DWG" + +#~ msgid "Comma-separated values" +#~ msgstr "Valeurs CSV séparées par des virgules" diff --git a/res/locales/ru_RU.po b/res/locales/ru_RU.po new file mode 100644 index 0000000..aa6078e --- /dev/null +++ b/res/locales/ru_RU.po @@ -0,0 +1,2026 @@ +# Russian translations for SolveSpace package. +# Copyright (C) 2017 the SolveSpace authors +# This file is distributed under the same license as the SolveSpace package. +# EvilSpirit , 2017. +msgid "" +msgstr "" +"Project-Id-Version: SolveSpace 3.0\n" +"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" +"POT-Creation-Date: 2020-11-17 20:50-0500\n" +"PO-Revision-Date: 2017-04-21 10:29+0700\n" +"Last-Translator: evilspirit@evilspirit.org\n" +"Language-Team: EvilSpirit\n" +"Language: ru_RU\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 2.0.1\n" + +#: clipboard.cpp:274 +msgid "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." +msgstr "" +"Копировать, вставить или вырезать\n" +"можно только находясь в рабочей плоскости.\n" +"Активируйте рабочую плоскость через Эскиз->В Рабочей Плоскости" + +#: clipboard.cpp:291 +msgid "Clipboard is empty; nothing to paste." +msgstr "Буфер обмена пуст; нечего вставлять." + +#: clipboard.cpp:338 +msgid "Number of copies to paste must be at least one." +msgstr "Укажите в поле 'количество' хотя бы одну копию для вставки." + +#: clipboard.cpp:354 textscreens.cpp:783 +msgid "Scale cannot be zero." +msgstr "Масштабный коэффициент не может быть нулевым." + +#: clipboard.cpp:396 +msgid "Select one point to define origin of rotation." +msgstr "Выберите одну точку в качестве центра вращения." + +#: clipboard.cpp:408 +msgid "Select two points to define translation vector." +msgstr "Выберите две точки, чтобы задать вектор смещения." + +#: clipboard.cpp:418 +msgid "" +"Transformation is identity. So all copies will be exactly on top of each " +"other." +msgstr "" +"Трансформация не задана. Все копии будут расположены в одном и том же месте." + +#: clipboard.cpp:422 +msgid "Too many items to paste; split this into smaller pastes." +msgstr "Слишком много элементов для вставки; разбейте на несколько частей." + +#: clipboard.cpp:427 +msgid "No workplane active." +msgstr "Рабочая плоскость не активна" + +#: confscreen.cpp:410 +msgid "Bad format: specify coordinates as x, y, z" +msgstr "Неверный формат: введите координаты как x, y, z" + +#: confscreen.cpp:420 style.cpp:659 textscreens.cpp:805 +msgid "Bad format: specify color as r, g, b" +msgstr "Неверный формат: введите цвет как r, g, b" + +#: confscreen.cpp:446 +msgid "" +"The perspective factor will have no effect until you enable View -> Use " +"Perspective Projection." +msgstr "" +"Коэффициент перспективы не будет иметь эффект, пока вы не включите Вид-" +">Перспективная Проекция." + +#: confscreen.cpp:459 confscreen.cpp:469 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "Введите число от 0 до %d." + +#: confscreen.cpp:481 +msgid "Export scale must not be zero!" +msgstr "Масштабный коэффициент не может быть нулевым!" + +#: confscreen.cpp:493 +msgid "Cutter radius offset must not be negative!" +msgstr "Радиус режущего инструмента не может быть отрицательным!" + +#: confscreen.cpp:547 +msgid "Bad value: autosave interval should be positive" +msgstr "" +"Неверное значение: интервал автосохранения должен быть положительным числом" + +#: confscreen.cpp:550 +msgid "Bad format: specify interval in integral minutes" +msgstr "" +"Неверный формат: введите целое число, чтобы задать интервал автосохранения" + +#: constraint.cpp:12 +msgctxt "constr-name" +msgid "pts-coincident" +msgstr "тчк-тчк-совпадение" + +#: constraint.cpp:13 +msgctxt "constr-name" +msgid "pt-pt-distance" +msgstr "тчк-тчк-расстояние" + +#: constraint.cpp:14 +msgctxt "constr-name" +msgid "pt-line-distance" +msgstr "тчк-линия-расстояние" + +#: constraint.cpp:15 +msgctxt "constr-name" +msgid "pt-plane-distance" +msgstr "тчк-плоск-расстояние" + +#: constraint.cpp:16 +msgctxt "constr-name" +msgid "pt-face-distance" +msgstr "тчк-грань-расстояние" + +#: constraint.cpp:17 +msgctxt "constr-name" +msgid "proj-pt-pt-distance" +msgstr "проекц-тчк-тчк-расст" + +#: constraint.cpp:18 +msgctxt "constr-name" +msgid "pt-in-plane" +msgstr "тчк-на-плоск" + +#: constraint.cpp:19 +msgctxt "constr-name" +msgid "pt-on-line" +msgstr "тчк-на-линии" + +#: constraint.cpp:20 +msgctxt "constr-name" +msgid "pt-on-face" +msgstr "тчк-на-грани" + +#: constraint.cpp:21 +msgctxt "constr-name" +msgid "eq-length" +msgstr "равенство-длин" + +#: constraint.cpp:22 +msgctxt "constr-name" +msgid "eq-length-and-pt-ln-dist" +msgstr "равен-длины-и-тчк-лин-расст" + +#: constraint.cpp:23 +msgctxt "constr-name" +msgid "eq-pt-line-distances" +msgstr "равен-тчк-линия-расстояний" + +#: constraint.cpp:24 +msgctxt "constr-name" +msgid "length-ratio" +msgstr "отношение-длин" + +#: constraint.cpp:25 +msgctxt "constr-name" +msgid "length-difference" +msgstr "разность-длин" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "symmetric" +msgstr "симметричность" + +#: constraint.cpp:27 +msgctxt "constr-name" +msgid "symmetric-h" +msgstr "симметричность-гориз" + +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "symmetric-v" +msgstr "симметричность-верт" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "symmetric-line" +msgstr "симметричность-по-оси" + +#: constraint.cpp:30 +msgctxt "constr-name" +msgid "at-midpoint" +msgstr "на-середине" + +#: constraint.cpp:31 +msgctxt "constr-name" +msgid "horizontal" +msgstr "горизонтальность" + +#: constraint.cpp:32 +msgctxt "constr-name" +msgid "vertical" +msgstr "вертикальность" + +#: constraint.cpp:33 +msgctxt "constr-name" +msgid "diameter" +msgstr "диаметр" + +#: constraint.cpp:34 +msgctxt "constr-name" +msgid "pt-on-circle" +msgstr "тчк-на-окружности" + +#: constraint.cpp:35 +msgctxt "constr-name" +msgid "same-orientation" +msgstr "идентичная-ориентация" + +#: constraint.cpp:36 +msgctxt "constr-name" +msgid "angle" +msgstr "угол" + +#: constraint.cpp:37 +msgctxt "constr-name" +msgid "parallel" +msgstr "параллельность" + +#: constraint.cpp:38 +msgctxt "constr-name" +msgid "arc-line-tangent" +msgstr "кас-дуга-линия" + +#: constraint.cpp:39 +msgctxt "constr-name" +msgid "cubic-line-tangent" +msgstr "кас-сплайн-линия" + +#: constraint.cpp:40 +msgctxt "constr-name" +msgid "curve-curve-tangent" +msgstr "кас-кривых" + +#: constraint.cpp:41 +msgctxt "constr-name" +msgid "perpendicular" +msgstr "перпендикулярность" + +#: constraint.cpp:42 +msgctxt "constr-name" +msgid "eq-radius" +msgstr "равенство-радиусов" + +#: constraint.cpp:43 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "равенство-углов" + +#: constraint.cpp:44 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "равен-длины-линии-длины-дуги" + +#: constraint.cpp:45 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "фиксация" + +#: constraint.cpp:46 +msgctxt "constr-name" +msgid "comment" +msgstr "комментарий" + +#: constraint.cpp:171 +msgid "" +"Bad selection for distance / diameter constraint. This constraint can apply " +"to:\n" +"\n" +" * two points (distance between points)\n" +" * a line segment (length)\n" +" * two points and a line segment or normal (projected distance)\n" +" * a workplane and a point (minimum distance)\n" +" * a line segment and a point (minimum distance)\n" +" * a plane face and a point (minimum distance)\n" +" * a circle or an arc (diameter)\n" +msgstr "" +"Неправильное выделение для ограничения расстояния / диаметра.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * две точки (расстояние между точками)\n" +" * отрезок (длина отрезка)\n" +" * две точки и отрезок / нормаль (расстояние между точками, " +"спроецированное на линию / нормаль)\n" +" * рабочую плоскость и точку (расстояние от точки до плоскости)\n" +" * отрезок и точку (расстояние от точки до линии)\n" +" * грань и точку (расстояние от точки до плоскости грани)\n" +" * окружность или дугу (диаметр / радиус)\n" + +#: constraint.cpp:224 +msgid "" +"Bad selection for on point / curve / plane constraint. This constraint can " +"apply to:\n" +"\n" +" * two points (points coincident)\n" +" * a point and a workplane (point in plane)\n" +" * a point and a line segment (point on line)\n" +" * a point and a circle or arc (point on curve)\n" +" * a point and a plane face (point on face)\n" +msgstr "" +"Неправильное выделение для ограничения 'точка на примитиве'.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * две точки (совпадение точек)\n" +" * точку и рабочую плоскость (точка в плоскости)\n" +" * точку и отрезок (точка на линии)\n" +" * точку и окружность / дугу / сплайн (точка на кривой)\n" +" * точку и грань (точка на грани)\n" + +#: constraint.cpp:286 +msgid "" +"Bad selection for equal length / radius constraint. This constraint can " +"apply to:\n" +"\n" +" * two line segments (equal length)\n" +" * two line segments and two points (equal point-line distances)\n" +" * a line segment and two points (equal point-line distances)\n" +" * a line segment, and a point and line segment (point-line distance " +"equals length)\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" +" * two circles or arcs (equal radius)\n" +" * a line segment and an arc (line segment length equals arc length)\n" +msgstr "" +"Неправильное выделение для ограничения 'равенство примитивов'.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * два отрезка (равенство длин отрезков)\n" +" * два отрезка и две точки (равенство расстояний от точек до линий)\n" +" * отрезок и две точки (равенство расстояний от точек до линии)\n" +" * отрезок, точку и отрезок (равенство длины отрезка расстоянию от точки " +"до линии)\n" +" * четыре отрезка или нормали (равенство углов A,B и C,D)\n" +" * три отрезка или нормали (равенство углов A,B and B,C)\n" +" * две окружности / дуги (равенство радиусов)\n" +" * отрезок и дугу (равенство длины отрезка и длины дуги)\n" + +#: constraint.cpp:325 +msgid "" +"Bad selection for length ratio constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +msgstr "" +"Неправильное выделение для ограничения 'отношение длин'.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * два отрезка\n" + +#: constraint.cpp:342 +msgid "" +"Bad selection for length difference constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments\n" +msgstr "" +"Неправильное выделение для ограничения 'разница длин'.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * два отрезка\n" + +#: constraint.cpp:368 +msgid "" +"Bad selection for at midpoint constraint. This constraint can apply to:\n" +"\n" +" * a line segment and a point (point at midpoint)\n" +" * a line segment and a workplane (line's midpoint on plane)\n" +msgstr "" +"Неправильное выделение для ограничения 'на середине'.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * точку и отрезок (точка на середине отрезка)\n" +" * отрезок и рабочую плоскость (середина отрезка на плоскости)\n" + +#: constraint.cpp:426 +msgid "" +"Bad selection for symmetric constraint. This constraint can apply to:\n" +"\n" +" * two points or a line segment (symmetric about workplane's coordinate " +"axis)\n" +" * line segment, and two points or a line segment (symmetric about line " +"segment)\n" +" * workplane, and two points or a line segment (symmetric about " +"workplane)\n" +msgstr "" +"Неправильное выделение для ограничения 'симметрия'.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * две точки / отрезок (симметричность точек по горизонтали/вертикали в " +"зависимости от вида)\n" +" * отрезок, две точки / отрезок (симметричность точек по оси отрезка)\n" +" * рабочую плоскость и две точки / отрезок (симметричность относительно " +"рабочей плоскости\n" + +#: constraint.cpp:440 +msgid "" +"A workplane must be active when constraining symmetric without an explicit " +"symmetry plane." +msgstr "" +"Рабочая плоскость должна быть активна для того, чтобы создать\n" +"ограничение симметричности без явного указания плоскости симметрии." + +#: constraint.cpp:470 +msgid "" +"Activate a workplane (with Sketch -> In Workplane) before applying a " +"horizontal or vertical constraint." +msgstr "" +"Рабочая плоскость должна быть активирована (Эскиз -> В рабочей плоскости)\n" +"перед тем, как накладывать ограничения горизонтальности / вертикальности." + +#: constraint.cpp:483 +msgid "" +"Bad selection for horizontal / vertical constraint. This constraint can " +"apply to:\n" +"\n" +" * two points\n" +" * a line segment\n" +msgstr "" +"Неправильное выделение для ограничения 'горизонтальность / вертикальность'.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * две точки\n" +" * отрезок\n" + +#: constraint.cpp:504 +msgid "" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" +"\n" +" * two normals\n" +msgstr "" +"Неправильное выделение для ограничения \"идентичная ориентация\".\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * два координатных базиса('нормали')\n" + +#: constraint.cpp:554 +msgid "Must select an angle constraint." +msgstr "" +"Переключатся между смежными углами можно только выбрав ограничение угла." + +#: constraint.cpp:567 +msgid "Must select a constraint with associated label." +msgstr "" +"Переключать режим 'размера для справок' возможно только для ограничений, " +"имеющих размерное значение." + +#: constraint.cpp:578 +msgid "" +"Bad selection for angle constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"Неправильное выделение для ограничения углового размера.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * два отрезка\n" +" * отрезок и координатный базис (нормаль)\n" +" * два координатных базиса (нормали)\n" + +#: constraint.cpp:635 +msgid "" +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent." +msgstr "" +"Дуга и отрезок должны быть соединены. Соедините их крайние точки с помощью " +"'Ограничения -> Точка на Примитиве' перед тем, как применять ограничение " +"касательности." + +#: constraint.cpp:659 +msgid "" +"The tangent cubic and line segment must share an endpoint. Constrain them " +"with Constrain -> On Point before constraining tangent." +msgstr "" +"Сплайн и отрезок должны быть соединены. Соедините их крайние точки с помощью " +"'Ограничения -> Точка на Примитиве' перед тем, как применять ограничение " +"касательности." + +#: constraint.cpp:669 +msgid "Curve-curve tangency must apply in workplane." +msgstr "" +"Ограничение касательности может быть наложено только в рабочей плоскости." + +#: constraint.cpp:687 +msgid "" +"The curves must share an endpoint. Constrain them with Constrain -> On Point " +"before constraining tangent." +msgstr "" +"Кривые должны быть соединены. Соедините их крайние точки с помощью " +"'Ограничения -> Точка на Примитиве' перед тем, как применять ограничение " +"касательности." + +#: constraint.cpp:696 +msgid "" +"Bad selection for parallel / tangent constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments (parallel)\n" +" * a line segment and a normal (parallel)\n" +" * two normals (parallel)\n" +" * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +msgstr "" +"Неправильное выделение для ограничения параллельности / касательности.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * два отрезка (параллельность)\n" +" * отрезок и координатный базис (нормаль) (параллельность)\n" +" * два координатных базиса (нормали) (параллельность)\n" +" * два отрезка, две дуги или два сплайна, соединенных крайними точками " +"(касательность)\n" + +#: constraint.cpp:714 +msgid "" +"Bad selection for perpendicular constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"Неправильное выделение для ограничения перпендикулярности.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * два отрезка\n" +" * отрезок и координатный базис (нормаль)\n" +" * два координатных базиса (нормали)\n" + +#: constraint.cpp:729 +msgid "" +"Bad selection for lock point where dragged constraint. This constraint can " +"apply to:\n" +"\n" +" * a point\n" +msgstr "" +"Неправильное выделение для ограничения 'Фиксация'.\n" +"Ограничение может принимать в качестве выделения следующие примитивы:\n" +"\n" +" * точку\n" + +#: constraint.cpp:740 +msgid "click center of comment text" +msgstr "кликните мышью там, где будет расположен текстовый комментарий" + +#: export.cpp:19 +msgid "" +"No solid model present; draw one with extrudes and revolves, or use Export " +"2d View to export bare lines and curves." +msgstr "" + +#: export.cpp:61 +msgid "" +"Bad selection for export section. Please select:\n" +"\n" +" * nothing, with an active workplane (workplane is section plane)\n" +" * a face (section plane through face)\n" +" * a point and two line segments (plane through point and parallel to " +"lines)\n" +msgstr "" + +#: export.cpp:822 +msgid "Active group mesh is empty; nothing to export." +msgstr "" + +#: exportvector.cpp:337 +msgid "freehand lines were replaced with continuous lines" +msgstr "Стили линии 'от руки' были заменены сплошными линиями" + +#: exportvector.cpp:339 +msgid "zigzag lines were replaced with continuous lines" +msgstr "Стили линии 'зиг-заг' были заменены сплошными линиями" + +#: exportvector.cpp:593 +msgid "" +"Some aspects of the drawing have no DXF equivalent and were not exported:\n" +msgstr "" +"Некоторые элементы чертежа не имеют аналогов в DXF-представлении и не были " +"экспортированы:\n" + +#: exportvector.cpp:839 +msgid "" +"PDF page size exceeds 200 by 200 inches; many viewers may reject this file." +msgstr "" +"Размер страницы PDF превышает 200x200 дюймов; некоторые программы просмотра " +"не смогут прочитать такой файл." + +#: file.cpp:44 group.cpp:91 +msgctxt "group-name" +msgid "sketch-in-plane" +msgstr "эскиз-в-плоскости" + +#: file.cpp:62 +msgctxt "group-name" +msgid "#references" +msgstr "система-координат" + +#: file.cpp:549 +msgid "" +"Unrecognized data in file. This file may be corrupt, or from a newer version " +"of the program." +msgstr "" +"Некоторые данные из этого файла не распознаны. Возможно, файл поврежден или " +"создан в более новой версии программы" + +#: file.cpp:859 +msgctxt "title" +msgid "Missing File" +msgstr "Файл Отсутствует" + +#: file.cpp:860 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "" + +#: file.cpp:862 +msgctxt "dialog" +msgid "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be " +"permanently removed." +msgstr "" +"Хотите найти их вручную?\n" +"Если вы ответите \"Нет\", то вся геометрия, которая зависит от " +"отсутствующего файла будет удалена." + +#: file.cpp:865 +msgctxt "button" +msgid "&Yes" +msgstr "Да" + +#: file.cpp:867 +msgctxt "button" +msgid "&No" +msgstr "Нет" + +#: file.cpp:869 +msgctxt "button" +msgid "&Cancel" +msgstr "Отменить" + +#: graphicswin.cpp:41 +msgid "&File" +msgstr "&Файл" + +#: graphicswin.cpp:42 +msgid "&New" +msgstr "&Новый" + +#: graphicswin.cpp:43 +msgid "&Open..." +msgstr "&Открыть..." + +#: graphicswin.cpp:44 +msgid "Open &Recent" +msgstr "Открыть Н&едавний" + +#: graphicswin.cpp:45 +msgid "&Save" +msgstr "&Сохранить" + +#: graphicswin.cpp:46 +msgid "Save &As..." +msgstr "Сохранить &Как..." + +#: graphicswin.cpp:48 +msgid "Export &Image..." +msgstr "Экспортировать И&зображение..." + +#: graphicswin.cpp:49 +msgid "Export 2d &View..." +msgstr "Экспортировать &2d Вид..." + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "Экспортировать 2d Се&чение..." + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "Экспортировать &3d Каркас..." + +#: graphicswin.cpp:52 +msgid "Export Triangle &Mesh..." +msgstr "Экспортировать &Полигональную сетку..." + +#: graphicswin.cpp:53 +msgid "Export &Surfaces..." +msgstr "Экспортировать Повер&хности..." + +#: graphicswin.cpp:54 +msgid "Im&port..." +msgstr "&Импортировать..." + +#: graphicswin.cpp:57 +msgid "E&xit" +msgstr "&Выход" + +#: graphicswin.cpp:60 +msgid "&Edit" +msgstr "&Правка" + +#: graphicswin.cpp:61 +msgid "&Undo" +msgstr "&Отменить" + +#: graphicswin.cpp:62 +msgid "&Redo" +msgstr "&Вернуть" + +#: graphicswin.cpp:63 +msgid "Re&generate All" +msgstr "&Перегенерировать Все" + +#: graphicswin.cpp:65 +msgid "Snap Selection to &Grid" +msgstr "Привязать Выделение к &Сетке" + +#: graphicswin.cpp:66 +msgid "Rotate Imported &90°" +msgstr "Повернуть Импортированное на &90°" + +#: graphicswin.cpp:68 +msgid "Cu&t" +msgstr "Вы&резать" + +#: graphicswin.cpp:69 +msgid "&Copy" +msgstr "&Копировать" + +#: graphicswin.cpp:70 +msgid "&Paste" +msgstr "В&ставить" + +#: graphicswin.cpp:71 +msgid "Paste &Transformed..." +msgstr "Вставить с &Трансформацией..." + +#: graphicswin.cpp:72 +msgid "&Delete" +msgstr "&Удалить" + +#: graphicswin.cpp:74 +msgid "Select &Edge Chain" +msgstr "Вы&делить Последовательность Ребер" + +#: graphicswin.cpp:75 +msgid "Select &All" +msgstr "В&ыделить Все" + +#: graphicswin.cpp:76 +msgid "&Unselect All" +msgstr "С&бросить Выделение" + +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "" + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "&View Прое&кция..." + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "" + +#: graphicswin.cpp:84 +msgid "&View" +msgstr "&Вид" + +#: graphicswin.cpp:85 +msgid "Zoom &In" +msgstr "&Приблизить" + +#: graphicswin.cpp:86 +msgid "Zoom &Out" +msgstr "От&далить" + +#: graphicswin.cpp:87 +msgid "Zoom To &Fit" +msgstr "Показать &Все / Выделенное" + +#: graphicswin.cpp:89 +msgid "Align View to &Workplane" +msgstr "Выровнять Вид на &Рабочую Плоскость" + +#: graphicswin.cpp:90 +msgid "Nearest &Ortho View" +msgstr "Ближайший &Ортогональный Вид" + +#: graphicswin.cpp:91 +msgid "Nearest &Isometric View" +msgstr "Ближайший &Изометрический Вид" + +#: graphicswin.cpp:92 +msgid "&Center View At Point" +msgstr "&Центрировать Вид на Точке" + +#: graphicswin.cpp:94 +msgid "Show Snap &Grid" +msgstr "Показать &Сетку" + +#: graphicswin.cpp:95 +msgid "Use &Perspective Projection" +msgstr "Перспективная Прое&кция" + +#: graphicswin.cpp:96 +msgid "Dimension &Units" +msgstr "" + +#: graphicswin.cpp:97 +msgid "Dimensions in &Millimeters" +msgstr "Размеры в Ми&ллиметрах" + +#: graphicswin.cpp:98 +msgid "Dimensions in M&eters" +msgstr "" + +#: graphicswin.cpp:99 +msgid "Dimensions in &Inches" +msgstr "Размеры в Дю&ймах" + +#: graphicswin.cpp:101 +msgid "Show &Toolbar" +msgstr "Показывать Па&нель Инструментов" + +#: graphicswin.cpp:102 +msgid "Show Property Bro&wser" +msgstr "Показывать Брау&зер" + +#: graphicswin.cpp:104 +msgid "&Full Screen" +msgstr "Полно&экранный Режим" + +#: graphicswin.cpp:106 +msgid "&New Group" +msgstr "&Группа" + +#: graphicswin.cpp:107 +msgid "Sketch In &3d" +msgstr "Создать Эскиз в &3d" + +#: graphicswin.cpp:108 +msgid "Sketch In New &Workplane" +msgstr "Создать Эскиз в Новой &Рабочей Плоскости" + +#: graphicswin.cpp:110 +msgid "Step &Translating" +msgstr "&Линейный Массив" + +#: graphicswin.cpp:111 +msgid "Step &Rotating" +msgstr "&Круговой Массив" + +#: graphicswin.cpp:113 +msgid "E&xtrude" +msgstr "Тело &Выдавливания" + +#: graphicswin.cpp:114 +msgid "&Helix" +msgstr "" + +#: graphicswin.cpp:115 +msgid "&Lathe" +msgstr "Тело В&ращения" + +#: graphicswin.cpp:116 +msgid "Re&volve" +msgstr "" + +#: graphicswin.cpp:118 +msgid "Link / Assemble..." +msgstr "&Импорт Детали / Сборка..." + +#: graphicswin.cpp:119 +msgid "Link Recent" +msgstr "Последние &Детали" + +#: graphicswin.cpp:121 +msgid "&Sketch" +msgstr "&Эскиз" + +#: graphicswin.cpp:122 +msgid "In &Workplane" +msgstr "В &Рабочей Плоскости" + +#: graphicswin.cpp:123 +msgid "Anywhere In &3d" +msgstr "Режим &3d" + +#: graphicswin.cpp:125 +msgid "Datum &Point" +msgstr "Опорная &Точка" + +#: graphicswin.cpp:126 +msgid "&Workplane" +msgstr "Рабочая &Плоскость" + +#: graphicswin.cpp:128 +msgid "Line &Segment" +msgstr "&Отрезок" + +#: graphicswin.cpp:129 +msgid "C&onstruction Line Segment" +msgstr "&Вспомогательный Отрезок" + +#: graphicswin.cpp:130 +msgid "&Rectangle" +msgstr "Прямоу&гольник" + +#: graphicswin.cpp:131 +msgid "&Circle" +msgstr "О&кружность" + +#: graphicswin.cpp:132 +msgid "&Arc of a Circle" +msgstr "Д&уга Окружности" + +#: graphicswin.cpp:133 +msgid "&Bezier Cubic Spline" +msgstr "Кубический &Сплайн Безье" + +#: graphicswin.cpp:135 +msgid "&Text in TrueType Font" +msgstr "Т&екст TrueType" + +#: graphicswin.cpp:136 +msgid "&Image" +msgstr "И&зображение" + +#: graphicswin.cpp:138 +msgid "To&ggle Construction" +msgstr "Переключить Режим Вс&помогательных Построений" + +#: graphicswin.cpp:139 +msgid "Tangent &Arc at Point" +msgstr "Кас&ательная в Точке" + +#: graphicswin.cpp:140 +msgid "Split Curves at &Intersection" +msgstr "Ра&збить Кривые Пересечением" + +#: graphicswin.cpp:142 +msgid "&Constrain" +msgstr "&Ограничения" + +#: graphicswin.cpp:143 +msgid "&Distance / Diameter" +msgstr "&Расстояние / Диаметр" + +#: graphicswin.cpp:144 +msgid "Re&ference Dimension" +msgstr "&Справочный Размер" + +#: graphicswin.cpp:145 +msgid "A&ngle" +msgstr "&Угол" + +#: graphicswin.cpp:146 +msgid "Reference An&gle" +msgstr "С&правочный Угол" + +#: graphicswin.cpp:147 +msgid "Other S&upplementary Angle" +msgstr "Переключить Сме&жный Угол" + +#: graphicswin.cpp:148 +msgid "Toggle R&eference Dim" +msgstr "Переключить Режим Размера Для Спра&вок" + +#: graphicswin.cpp:150 +msgid "&Horizontal" +msgstr "&Горизонтальность" + +#: graphicswin.cpp:151 +msgid "&Vertical" +msgstr "&Вертикальность" + +#: graphicswin.cpp:153 +msgid "&On Point / Curve / Plane" +msgstr "&Точка на Примитиве" + +#: graphicswin.cpp:154 +msgid "E&qual Length / Radius / Angle" +msgstr "&Равенство Длин / Радиусов / Углов" + +#: graphicswin.cpp:155 +msgid "Length Ra&tio" +msgstr "Отно&шение Длин" + +#: graphicswin.cpp:156 +msgid "Length Diff&erence" +msgstr "Ра&зница Длин" + +#: graphicswin.cpp:157 +msgid "At &Midpoint" +msgstr "&На Середине" + +#: graphicswin.cpp:158 +msgid "S&ymmetric" +msgstr "С&имметричность" + +#: graphicswin.cpp:159 +msgid "Para&llel / Tangent" +msgstr "Пара&ллельность / Касательность" + +#: graphicswin.cpp:160 +msgid "&Perpendicular" +msgstr "Перпендикул&ярность" + +#: graphicswin.cpp:161 +msgid "Same Orient&ation" +msgstr "Идентичная &Ориентация" + +#: graphicswin.cpp:162 +msgid "Lock Point Where &Dragged" +msgstr "За&фиксировать" + +#: graphicswin.cpp:164 +msgid "Comment" +msgstr "Текстовый &Комментарий" + +#: graphicswin.cpp:166 +msgid "&Analyze" +msgstr "&Анализ" + +#: graphicswin.cpp:167 +msgid "Measure &Volume" +msgstr "Измерить &Объем" + +#: graphicswin.cpp:168 +msgid "Measure A&rea" +msgstr "Измерить П&лощадь" + +#: graphicswin.cpp:169 +msgid "Measure &Perimeter" +msgstr "Измерить П&ериметр" + +#: graphicswin.cpp:170 +msgid "Show &Interfering Parts" +msgstr "Показать Пе&ресекающиеся Детали" + +#: graphicswin.cpp:171 +msgid "Show &Naked Edges" +msgstr "Показать Про&блемные Ребра" + +#: graphicswin.cpp:172 +msgid "Show &Center of Mass" +msgstr "" + +#: graphicswin.cpp:174 +msgid "Show &Underconstrained Points" +msgstr "" + +#: graphicswin.cpp:176 +msgid "&Trace Point" +msgstr "Включить &Трассировку Точки" + +#: graphicswin.cpp:177 +msgid "&Stop Tracing..." +msgstr "Остановить Тра&ссировку..." + +#: graphicswin.cpp:178 +msgid "Step &Dimension..." +msgstr "Плавное Из&менение Размера..." + +#: graphicswin.cpp:180 +msgid "&Help" +msgstr "&Помощь" + +#: graphicswin.cpp:181 +msgid "&Language" +msgstr "&Язык" + +#: graphicswin.cpp:182 +msgid "&Website / Manual" +msgstr "Вебсайт / &Справка" + +#: graphicswin.cpp:184 +msgid "&About" +msgstr "О &Программе" + +#: graphicswin.cpp:352 +msgid "(no recent files)" +msgstr "(пусто)" + +#: graphicswin.cpp:360 +#, c-format +msgid "File '%s' does not exist." +msgstr "" + +#: graphicswin.cpp:721 +msgid "No workplane is active, so the grid will not appear." +msgstr "Сетку не будет видно, пока рабочая плоскость не активирована." + +#: graphicswin.cpp:730 +msgid "" +"The perspective factor is set to zero, so the view will always be a parallel " +"projection.\n" +"\n" +"For a perspective projection, modify the perspective factor in the " +"configuration screen. A value around 0.3 is typical." +msgstr "" +"Коэффициент перспективы установлен в ноль, что соответствует ортогональной " +"проекции.\n" +"Чтобы получить перспективную проекцию, отредактируйте значение коэффициента " +"перспективы на конфигурационной странице браузера.\n" +"Значение по умолчанию 0.3." + +#: graphicswin.cpp:809 +msgid "" +"Select a point; this point will become the center of the view on screen." +msgstr "Выделите точку. Вид будет отцентрован по этой точке." + +#: graphicswin.cpp:1103 +msgid "No additional entities share endpoints with the selected entities." +msgstr "Нет дополнительных объектов, соединенных с выбранными примитивами." + +#: graphicswin.cpp:1121 +msgid "" +"To use this command, select a point or other entity from an linked part, or " +"make a link group the active group." +msgstr "" +"Чтобы использовать эту команду, выделите точку или другой примитив, " +"принадлежащий импортированной детали или активируйте группу импортированной " +"детали." + +#: graphicswin.cpp:1144 +msgid "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) " +"to define the plane for the snap grid." +msgstr "" +"Рабочая плоскость не активна. Активируйте ее через Эскиз -> В Рабочей " +"Плоскости чтобы определить плоскость для сетки." + +#: graphicswin.cpp:1151 +msgid "" +"Can't snap these items to grid; select points, text comments, or constraints " +"with a label. To snap a line, select its endpoints." +msgstr "" +"Невозможно привязать выбранные примитивы к сетке. Необходимо выбирать точки, " +"текстовые комментарии или ограничения с размерными значениями. Чтобы " +"привязать отрезок или другой примитив, выбирайте его точки." + +#: graphicswin.cpp:1239 +msgid "No workplane selected. Activating default workplane for this group." +msgstr "" +"Рабочая плоскость не активна. Активирована рабочая плоскость по умолчанию " +"для данной группы." + +#: graphicswin.cpp:1242 +msgid "" +"No workplane is selected, and the active group does not have a default " +"workplane. Try selecting a workplane, or activating a sketch-in-new-" +"workplane group." +msgstr "" +"Рабочая плоскость не выбрана и активная группа не содержит рабочей плоскости " +"по умолчанию. Попробуйте выделить рабочую плоскость или создать новую с " +"помощью Группа -> Создать Эскиз в Новой Рабочей Плоскости." + +#: graphicswin.cpp:1263 +msgid "" +"Bad selection for tangent arc at point. Select a single point, or select " +"nothing to set up arc parameters." +msgstr "" +"Неправильное выделение для создания скругления в точке. Выделите либо одну " +"точку, либо запустите команду без выделения, чтобы перейти к окну настроек " +"этой команды." + +#: graphicswin.cpp:1274 +msgid "click point on arc (draws anti-clockwise)" +msgstr "" +"кликните мышью там, где хотите создать дугу окружности (дуга будет " +"нарисована против часовой стрелки)" + +#: graphicswin.cpp:1275 +msgid "click to place datum point" +msgstr "кликните мышью там, где хотите создать опорную точку" + +#: graphicswin.cpp:1276 +msgid "click first point of line segment" +msgstr "кликните мышью там, где хотите создать первую точку отрезка" + +#: graphicswin.cpp:1278 +msgid "click first point of construction line segment" +msgstr "" +"кликните мышью там, где хотите создать первую точку вспомогательного отрезка" + +#: graphicswin.cpp:1279 +msgid "click first point of cubic segment" +msgstr "" +"кликните мышью там, где хотите создать первую точку кубического сплайна Безье" + +#: graphicswin.cpp:1280 +msgid "click center of circle" +msgstr "кликните мышью там, где будет находиться центр окружности" + +#: graphicswin.cpp:1281 +msgid "click origin of workplane" +msgstr "" +"кликните мышью там, где будет находиться точка, через которую будет " +"построена рабочая плоскость" + +#: graphicswin.cpp:1282 +msgid "click one corner of rectangle" +msgstr "кликните мышью там, где будет находиться один из углов прямоугольника" + +#: graphicswin.cpp:1283 +msgid "click top left of text" +msgstr "кликните мышью там, где хотите создать текст" + +#: graphicswin.cpp:1289 +msgid "click top left of image" +msgstr "" + +#: graphicswin.cpp:1301 +msgid "" +"No entities are selected. Select entities before trying to toggle their " +"construction state." +msgstr "" +"Не выбран ни один примитив. Выберите примитивы перед тем, как переключать их " +"режим дополнительных построений." + +#: group.cpp:86 +msgctxt "group-name" +msgid "sketch-in-3d" +msgstr "эскиз-в-3d" + +#: group.cpp:142 +msgid "" +"Bad selection for new sketch in workplane. This group can be created with:\n" +"\n" +" * a point (through the point, orthogonal to coordinate axes)\n" +" * a point and two line segments (through the point, parallel to the " +"lines)\n" +" * a workplane (copy of the workplane)\n" +msgstr "" +"Неправильное выделение для создания эскиза.\n" +"Группа может быть создана, используя в качестве выделения следующие " +"примитивы:\n" +"\n" +" * точку (рабочая плоскость, ориентированная к ближайшему виду, " +"проходящая через точку)\n" +" * точку и два отрезка (рабочая плоскость, проходящая через точку и " +"параллельная отрезкам)\n" +" * рабочую плоскость (копия рабочей плоскости)\n" + +#: group.cpp:154 +msgid "" +"Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " +"will be extruded normal to the workplane." +msgstr "" +"Выберите рабочую плоскость (Эскиз -> В Рабочей Плоскости) перед созданием " +"группы выдавливания. Эскиз будет выдавлен по нормали к рабочей плоскости." + +#: group.cpp:163 +msgctxt "group-name" +msgid "extrude" +msgstr "тело-выдавливания" + +#: group.cpp:168 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:179 +msgid "" +"Bad selection for new lathe group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"Неправильное выделение для создания группы тела вращения. \n" +"Группа может быть создана, используя в качестве выделения следующие " +"примитивы:\n" +"\n" +" * точку и отрезок / координатных базис (нормаль) (тело вращения вокруг " +"оси, проходящей через точку и параллельной отрезку / нормали)\n" +" * отрезок (тело вращения вокруг оси, проходящей через отрезок)\n" +"\n" + +#: group.cpp:189 +msgctxt "group-name" +msgid "lathe" +msgstr "тело-вращения" + +#: group.cpp:194 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:205 +msgid "" +"Bad selection for new revolve group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:217 +msgctxt "group-name" +msgid "revolve" +msgstr "" + +#: group.cpp:222 +msgid "Helix operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:233 +msgid "" +"Bad selection for new helix group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:245 +msgctxt "group-name" +msgid "helix" +msgstr "" + +#: group.cpp:258 +msgid "" +"Bad selection for new rotation. This group can be created with:\n" +"\n" +" * a point, while locked in workplane (rotate in plane, about that " +"point)\n" +" * a point and a line or a normal (rotate about an axis through the " +"point, and parallel to line / normal)\n" +msgstr "" +"Неправильное выделение для создания группы кругового массива. \n" +"Группа может быть создана, используя в качестве выделения следующие " +"примитивы:\n" +"\n" +" * точку при активной рабочей плоскости (вращение в плоскости вокруг " +"выбранной точки)\n" +" * точку и отрезок / координатных базис (нормаль) (вращение вокруг оси, " +"проходящей через точку и параллельной отрезку / нормали)\n" +"\n" + +#: group.cpp:271 +msgctxt "group-name" +msgid "rotate" +msgstr "круговой-массив" + +#: group.cpp:282 +msgctxt "group-name" +msgid "translate" +msgstr "линейный-массив" + +#: group.cpp:400 +msgid "(unnamed)" +msgstr "(без имени)" + +#: groupmesh.cpp:708 +msgid "not closed contour, or not all same style!" +msgstr "незамкнутый контур или несовпадение стилей!" + +#: groupmesh.cpp:721 +msgid "points not all coplanar!" +msgstr "не все точки лежат в одной плоскости!" + +#: groupmesh.cpp:723 +msgid "contour is self-intersecting!" +msgstr "контур имеет самопересечения!" + +#: groupmesh.cpp:725 +msgid "zero-length edge!" +msgstr "вырожденный отрезок!" + +#: modify.cpp:254 +msgid "Must be sketching in workplane to create tangent arc." +msgstr "" +"Скругления эскиза можно создавать только когда рабочая плоскость " +"активирована." + +#: modify.cpp:301 +msgid "" +"To create a tangent arc, select a point where two non-construction lines or " +"circles in this group and workplane join." +msgstr "" +"Чтобы создать скругление эскиза, выберите точку, где соединяются два " +"примитива, не принадлежащих к вспомогательной геометрии." + +#: modify.cpp:388 +msgid "" +"Couldn't round this corner. Try a smaller radius, or try creating the " +"desired geometry by hand with tangency constraints." +msgstr "" +"Невозможно скруглить угол. Попробуйте радиус поменьше или создайте требуемую " +"геометрию с помощью Ограничения -> Параллельность / Касательность." + +#: modify.cpp:597 +msgid "Couldn't split this entity; lines, circles, or cubics only." +msgstr "" +"Невозможно разделить такие примитивы. Выберите линии, окружности или " +"кубические сплайны." + +#: modify.cpp:624 +msgid "Must be sketching in workplane to split." +msgstr "" +"Пересечение примитивов работает только когда рабочая плоскость активна." + +#: modify.cpp:631 +msgid "" +"Select two entities that intersect each other (e.g. two lines/circles/arcs " +"or a line/circle/arc and a point)." +msgstr "" + +#: modify.cpp:736 +msgid "Can't split; no intersection found." +msgstr "Невозможно разделить пересекаемые примитивы: пересечений нет." + +#: mouse.cpp:560 +msgid "Assign to Style" +msgstr "Применить Стиль" + +#: mouse.cpp:576 +msgid "No Style" +msgstr "Стиль по Умолчанию" + +#: mouse.cpp:579 +msgid "Newly Created Custom Style..." +msgstr "Создать Новый Стиль..." + +#: mouse.cpp:586 +msgid "Group Info" +msgstr "Настройки Группы" + +#: mouse.cpp:606 +msgid "Style Info" +msgstr "Настройки Стиля" + +#: mouse.cpp:626 +msgid "Select Edge Chain" +msgstr "Выделить Последовательность Примитивов" + +#: mouse.cpp:632 +msgid "Toggle Reference Dimension" +msgstr "Переключить Режим Размера Для Справок" + +#: mouse.cpp:638 +msgid "Other Supplementary Angle" +msgstr "Переключить Смежный Угол" + +#: mouse.cpp:643 +msgid "Snap to Grid" +msgstr "Привязать к Сетке" + +#: mouse.cpp:652 +msgid "Remove Spline Point" +msgstr "Удалить Точку Сплайна" + +#: mouse.cpp:687 +msgid "Add Spline Point" +msgstr "Добавить Точку Сплайна" + +#: mouse.cpp:691 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "" +"Невозможно добавить точку сплайна: достигнуто ограничение максимального " +"количества точек для сплайна." + +#: mouse.cpp:716 +msgid "Toggle Construction" +msgstr "Переключить Режим 'Дополнительные Построения'." + +#: mouse.cpp:731 +msgid "Delete Point-Coincident Constraint" +msgstr "Удалить Ограничение Совпадения Точек" + +#: mouse.cpp:750 +msgid "Cut" +msgstr "Вырезать" + +#: mouse.cpp:752 +msgid "Copy" +msgstr "Копировать" + +#: mouse.cpp:756 +msgid "Select All" +msgstr "Выделить Все" + +#: mouse.cpp:761 +msgid "Paste" +msgstr "Вставить" + +#: mouse.cpp:763 +msgid "Paste Transformed..." +msgstr "Вставить с Трансформацией..." + +#: mouse.cpp:768 +msgid "Delete" +msgstr "Удалить" + +#: mouse.cpp:771 +msgid "Unselect All" +msgstr "Сбросить Выделение" + +#: mouse.cpp:778 +msgid "Unselect Hovered" +msgstr "Снять Выделение с Выбранного" + +#: mouse.cpp:787 +msgid "Zoom to Fit" +msgstr "Показать Все" + +#: mouse.cpp:990 mouse.cpp:1277 +msgid "click next point of line, or press Esc" +msgstr "кликните мышью там, где хотите расположить следующую точку" + +#: mouse.cpp:996 +msgid "" +"Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" +"Невозможно начертить прямоугольник, когда рабочая плоскость не активна." + +#: mouse.cpp:1030 +msgid "click to place other corner of rectangle" +msgstr "кликните мышью там, где хотите расположить другой угол прямоугольника" + +#: mouse.cpp:1050 +msgid "click to set radius" +msgstr "кликните, чтобы задать радиус" + +#: mouse.cpp:1055 +msgid "" +"Can't draw arc in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "Невозможно создать дугу, когда нет активной рабочей плоскости." + +#: mouse.cpp:1074 +msgid "click to place point" +msgstr "кликните мышью там, где хотите создать точку" + +#: mouse.cpp:1090 +msgid "click next point of cubic, or press Esc" +msgstr "" +"кликните мышью там, где хотите создать следующую точку сплайна или нажмите " +"Esc для завершения операции." + +#: mouse.cpp:1095 +msgid "" +"Sketching in a workplane already; sketch in 3d before creating new workplane." +msgstr "" +"Рабочая плоскость уже активна. Перейдите в режим 3d перед созданием новой " +"рабочей плоскости." + +#: mouse.cpp:1111 +msgid "" +"Can't draw text in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "Невозможно создать текст, когда нет активной рабочей плоскости." + +#: mouse.cpp:1128 +msgid "click to place bottom right of text" +msgstr "" + +#: mouse.cpp:1134 +msgid "" +"Can't draw image in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" + +#: mouse.cpp:1161 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "КОММЕНТАРИЙ -- ДВОЙНОЙ ЩЕЛЧОК ДЛЯ РЕДАКТИРОВАНИЯ" + +#: platform/gui.cpp:85 platform/gui.cpp:89 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "проекты SolveSpace" + +#: platform/gui.cpp:90 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "" + +#: platform/gui.cpp:94 +msgctxt "file-type" +msgid "PNG image" +msgstr "PNG изображение" + +#: platform/gui.cpp:98 +msgctxt "file-type" +msgid "STL mesh" +msgstr "STL полигональная сетка" + +#: platform/gui.cpp:99 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Wavefront OBJ полигональная сетка" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "Three.js-совместимая полигональная сетка с просмторщиком" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "Three.js-совместимая полигональная сетка" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Q3D Object file" +msgstr "" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "VRML text file" +msgstr "" + +#: platform/gui.cpp:107 platform/gui.cpp:114 platform/gui.cpp:121 +msgctxt "file-type" +msgid "STEP file" +msgstr "STEP файл" + +#: platform/gui.cpp:111 +msgctxt "file-type" +msgid "PDF file" +msgstr "PDF документ" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "Encapsulated PostScript" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "SVG изображение" + +#: platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "DXF файл (AutoCAD 2007)" + +#: platform/gui.cpp:116 +msgctxt "file-type" +msgid "HPGL file" +msgstr "HPGL файл" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "G Code" +msgstr "G Code" + +#: platform/gui.cpp:126 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "AutoCAD DXF и DWG файлы" + +#: platform/gui.cpp:130 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "CSV файлы (значения, разделенные запятой)" + +#: platform/guigtk.cpp:1317 platform/guimac.mm:1360 platform/guiwin.cpp:1608 +msgid "untitled" +msgstr "без имени" + +#: platform/guigtk.cpp:1328 platform/guigtk.cpp:1361 platform/guimac.mm:1318 +#: platform/guiwin.cpp:1555 +msgctxt "title" +msgid "Save File" +msgstr "Сохранить Файл" + +#: platform/guigtk.cpp:1329 platform/guigtk.cpp:1362 platform/guimac.mm:1301 +#: platform/guiwin.cpp:1557 +msgctxt "title" +msgid "Open File" +msgstr "Открыть Файл" + +#: platform/guigtk.cpp:1332 platform/guigtk.cpp:1368 +msgctxt "button" +msgid "_Cancel" +msgstr "Отменить" + +#: platform/guigtk.cpp:1333 platform/guigtk.cpp:1366 +msgctxt "button" +msgid "_Save" +msgstr "Сохранить" + +#: platform/guigtk.cpp:1334 platform/guigtk.cpp:1367 +msgctxt "button" +msgid "_Open" +msgstr "" + +#: style.cpp:166 +msgid "" +"Can't assign style to an entity that's derived from another entity; try " +"assigning a style to this entity's parent." +msgstr "" +"Невозможно применить стиль к примитиву, который произошел от другого " +"примитива. Попробуйте применить стиль к исходному примитиву." + +#: style.cpp:665 +msgid "Style name cannot be empty" +msgstr "Имя стиля не может быть пустым." + +#: textscreens.cpp:741 +msgid "Can't repeat fewer than 1 time." +msgstr "Невозможно сделать повторение меньше, чем 1 раз." + +#: textscreens.cpp:745 +msgid "Can't repeat more than 999 times." +msgstr "Невозможно сделать повтор больше, чем 999 раз." + +#: textscreens.cpp:770 +msgid "Group name cannot be empty" +msgstr "Имя группы не может быть пустым." + +#: textscreens.cpp:813 +msgid "Opacity must be between zero and one." +msgstr "Прозрачность должна быть числом от нуля до единицы." + +#: textscreens.cpp:848 +msgid "Radius cannot be zero or negative." +msgstr "Радиус не может быть нулевым или отрицательным." + +#: toolbar.cpp:18 +msgid "Sketch line segment" +msgstr "Начертить отрезок прямой" + +#: toolbar.cpp:20 +msgid "Sketch rectangle" +msgstr "Начертить прямоугольник" + +#: toolbar.cpp:22 +msgid "Sketch circle" +msgstr "Начертить окружность" + +#: toolbar.cpp:24 +msgid "Sketch arc of a circle" +msgstr "Начертить дугу окружности" + +#: toolbar.cpp:26 +msgid "Sketch curves from text in a TrueType font" +msgstr "Начертить текст TrueType" + +#: toolbar.cpp:28 +msgid "Sketch image from a file" +msgstr "" + +#: toolbar.cpp:30 +msgid "Create tangent arc at selected point" +msgstr "Создать скругление в выбранной точке" + +#: toolbar.cpp:32 +msgid "Sketch cubic Bezier spline" +msgstr "Начертить кубический сплайн Безье" + +#: toolbar.cpp:34 +msgid "Sketch datum point" +msgstr "Начертить опорную точку" + +#: toolbar.cpp:36 +msgid "Toggle construction" +msgstr "Переключить вспомогательную геометрию" + +#: toolbar.cpp:38 +msgid "Split lines / curves where they intersect" +msgstr "Разбить кривые по пересечению" + +#: toolbar.cpp:42 +msgid "Constrain distance / diameter / length" +msgstr "Ограничение расстояния / диаметра / длины" + +#: toolbar.cpp:44 +msgid "Constrain angle" +msgstr "Ограничение угла" + +#: toolbar.cpp:46 +msgid "Constrain to be horizontal" +msgstr "Ограничение горизонтальности" + +#: toolbar.cpp:48 +msgid "Constrain to be vertical" +msgstr "Ограничение вертикальности" + +#: toolbar.cpp:50 +msgid "Constrain to be parallel or tangent" +msgstr "Ограничение параллельности / касательности" + +#: toolbar.cpp:52 +msgid "Constrain to be perpendicular" +msgstr "Ограничение перпендикулярности" + +#: toolbar.cpp:54 +msgid "Constrain point on line / curve / plane / point" +msgstr "Ограничение точка на точке / линии / окружности / дуге / плоскости" + +#: toolbar.cpp:56 +msgid "Constrain symmetric" +msgstr "Симметричность" + +#: toolbar.cpp:58 +msgid "Constrain equal length / radius / angle" +msgstr "Ограничение равенства длин / радиусов / углов" + +#: toolbar.cpp:60 +msgid "Constrain normals in same orientation" +msgstr "Ограничение идентичности ориентации координатных базисов" + +#: toolbar.cpp:62 +msgid "Other supplementary angle" +msgstr "Переключить смежный угол" + +#: toolbar.cpp:64 +msgid "Toggle reference dimension" +msgstr "Переключить режим размера для справок" + +#: toolbar.cpp:68 +msgid "New group extruding active sketch" +msgstr "Создать группу выдавливания текущего эскиза" + +#: toolbar.cpp:70 +msgid "New group rotating active sketch" +msgstr "Создать группу вращения текущего эскиза" + +#: toolbar.cpp:72 +msgid "New group step and repeat rotating" +msgstr "Создать группу кругового массива" + +#: toolbar.cpp:74 +msgid "New group step and repeat translating" +msgstr "Создать группу линейного массива" + +#: toolbar.cpp:76 +msgid "New group in new workplane (thru given entities)" +msgstr "Создать группу в новой рабочей плоскости (через выбранные примитивы)" + +#: toolbar.cpp:78 +msgid "New group in 3d" +msgstr "Новая группа в 3d" + +#: toolbar.cpp:80 +msgid "New group linking / assembling file" +msgstr "Новая группа импорта детали / сборки" + +#: toolbar.cpp:84 +msgid "Nearest isometric view" +msgstr "Ближайший изометрический вид" + +#: toolbar.cpp:86 +msgid "Align view to active workplane" +msgstr "Выровнять вид на рабочую плоскость" + +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "Ошибка" + +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "Сообщение" + +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "ХОРОШО" + +#: view.cpp:78 +msgid "Scale cannot be zero or negative." +msgstr "Масштабный коэффициент не может быть нулевым или отрицательным." + +#: view.cpp:90 view.cpp:99 +msgid "Bad format: specify x, y, z" +msgstr "Неверный формат: введите данные как x, y, z" + +#~ msgctxt "title" +#~ msgid "(new sketch)" +#~ msgstr "(новый проект)" + +#~ msgctxt "title" +#~ msgid "Property Browser" +#~ msgstr "Браузер" + +#~ msgid "Specify between 0 and 8 digits after the decimal." +#~ msgstr "Введите число от 0 до 8." + +#~ msgid "Show Degrees of &Freedom" +#~ msgstr "Показать Степени С&вободы" + +#~ msgid "click to place bottom left of text" +#~ msgstr "кликните мышью, чтобы расположить текст" + +#~ msgid "Do you want to save the changes you made to the new sketch?" +#~ msgstr "Хотите сохранить ваши изменения?" + +#~ msgid "Your changes will be lost if you don't save them." +#~ msgstr "Ваши изменения будут утеряны, если вы их не сохраните." + +#~ msgctxt "button" +#~ msgid "Save" +#~ msgstr "Сохранить" + +#~ msgctxt "button" +#~ msgid "Cancel" +#~ msgstr "Отменить" + +#~ msgctxt "button" +#~ msgid "Don't Save" +#~ msgstr "Не Сохранять" + +#~ msgid "An autosave file is available for this project." +#~ msgstr "Файлы автосохранения доступны для данного проекта." + +#~ msgid "Do you want to load the autosave file instead?" +#~ msgstr "Хотите загрузить их вместо открываемого файла?" + +#~ msgctxt "button" +#~ msgid "Load" +#~ msgstr "Загрузить Автосохранение" + +#~ msgctxt "button" +#~ msgid "Don't Load" +#~ msgstr "Просто Открыть Файл" + +#~ msgid "" +#~ "Do you want to locate it manually?\n" +#~ "If you select “No”, any geometry that depends on the missing file will be " +#~ "removed." +#~ msgstr "" +#~ "Хотите найти их вручную?\n" +#~ "Если вы ответите \"Нет\", то вся геометрия, которая зависит от " +#~ "отсутствующего файла будет удалена." + +#~ msgctxt "button" +#~ msgid "Yes" +#~ msgstr "Да" + +#~ msgctxt "button" +#~ msgid "No" +#~ msgstr "Нет" + +#~ msgctxt "button" +#~ msgid "OK" +#~ msgstr "ХОРОШО" + +#~ msgid "_Cancel" +#~ msgstr "Отменить" + +#~ msgid "_Open" +#~ msgstr "Открыть" + +#~ msgid "" +#~ "The file has changed since it was last saved.\n" +#~ "\n" +#~ "Do you want to save the changes?" +#~ msgstr "" +#~ "Файл имеет несохраненные изменения.\n" +#~ "\n" +#~ "Хотите сохранить их?" + +#~ msgctxt "title" +#~ msgid "Modified File" +#~ msgstr "Измененный Файл" + +#~ msgctxt "button" +#~ msgid "Do_n't Save" +#~ msgstr "Не Сохранять" + +#~ msgid "" +#~ "An autosave file is available for this project.\n" +#~ "\n" +#~ "Do you want to load the autosave file instead?" +#~ msgstr "" +#~ "Файлы автосохранения доступны для данного проекта.\n" +#~ "\n" +#~ "Хотите загрузить их вместо открываемого файла?" + +#~ msgctxt "title" +#~ msgid "Autosave Available" +#~ msgstr "Автосохранение Доступно" + +#~ msgctxt "button" +#~ msgid "_Load autosave" +#~ msgstr "Загрузить Автосохранение" + +#~ msgctxt "button" +#~ msgid "Do_n't Load" +#~ msgstr "Не Загружать" + +#~ msgctxt "button" +#~ msgid "_Yes" +#~ msgstr "Да" + +#~ msgctxt "button" +#~ msgid "_No" +#~ msgstr "Нет" + +#~ msgid "" +#~ "Select two entities that intersect each other (e.g. two lines or two " +#~ "circles or a circle and a line)." +#~ msgstr "" +#~ "Выберите два пересекающихся примитива. Например, две линии или две " +#~ "окружности или линию и окружность." + +#~ msgid "Show Menu &Bar" +#~ msgstr "Показывать Мен&ю" + +#~ msgctxt "group-name" +#~ msgid "link" +#~ msgstr "ссылка-на-деталь" + +#~ msgid "Scale must not be zero or negative!" +#~ msgstr "" +#~ "Коэффициент масштабирования не может быть нулевым или отрицательным!" diff --git a/res/locales/uk_UA.po b/res/locales/uk_UA.po new file mode 100644 index 0000000..e987066 --- /dev/null +++ b/res/locales/uk_UA.po @@ -0,0 +1,1697 @@ +# Ukrainian translations for SolveSpace package. +# Copyright (C) 2017 the SolveSpace authors +# This file is distributed under the same license as the SolveSpace package. +# AppSoft4 , 2017. +msgid "" +msgstr "" +"Project-Id-Version: SolveSpace 3.0\n" +"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" +"POT-Creation-Date: 2020-11-17 20:50-0500\n" +"PO-Revision-Date: 2017-01-05 10:30+0000\n" +"Last-Translator: appsoft@ua.fm\n" +"Language-Team: OpenOrienteeringUkraine\n" +"Language: uk_UA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: clipboard.cpp:274 +msgid "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." +msgstr "" + +#: clipboard.cpp:291 +msgid "Clipboard is empty; nothing to paste." +msgstr "" + +#: clipboard.cpp:338 +msgid "Number of copies to paste must be at least one." +msgstr "" + +#: clipboard.cpp:354 textscreens.cpp:783 +msgid "Scale cannot be zero." +msgstr "" + +#: clipboard.cpp:396 +msgid "Select one point to define origin of rotation." +msgstr "" + +#: clipboard.cpp:408 +msgid "Select two points to define translation vector." +msgstr "" + +#: clipboard.cpp:418 +msgid "" +"Transformation is identity. So all copies will be exactly on top of each " +"other." +msgstr "" + +#: clipboard.cpp:422 +msgid "Too many items to paste; split this into smaller pastes." +msgstr "" + +#: clipboard.cpp:427 +msgid "No workplane active." +msgstr "" + +#: confscreen.cpp:410 +msgid "Bad format: specify coordinates as x, y, z" +msgstr "" + +#: confscreen.cpp:420 style.cpp:659 textscreens.cpp:805 +msgid "Bad format: specify color as r, g, b" +msgstr "" + +#: confscreen.cpp:446 +msgid "" +"The perspective factor will have no effect until you enable View -> Use " +"Perspective Projection." +msgstr "" + +#: confscreen.cpp:459 confscreen.cpp:469 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "" + +#: confscreen.cpp:481 +msgid "Export scale must not be zero!" +msgstr "" + +#: confscreen.cpp:493 +msgid "Cutter radius offset must not be negative!" +msgstr "" + +#: confscreen.cpp:547 +msgid "Bad value: autosave interval should be positive" +msgstr "" + +#: confscreen.cpp:550 +msgid "Bad format: specify interval in integral minutes" +msgstr "" + +#: constraint.cpp:12 +msgctxt "constr-name" +msgid "pts-coincident" +msgstr "" + +#: constraint.cpp:13 +msgctxt "constr-name" +msgid "pt-pt-distance" +msgstr "" + +#: constraint.cpp:14 +msgctxt "constr-name" +msgid "pt-line-distance" +msgstr "" + +#: constraint.cpp:15 +msgctxt "constr-name" +msgid "pt-plane-distance" +msgstr "" + +#: constraint.cpp:16 +msgctxt "constr-name" +msgid "pt-face-distance" +msgstr "" + +#: constraint.cpp:17 +msgctxt "constr-name" +msgid "proj-pt-pt-distance" +msgstr "" + +#: constraint.cpp:18 +msgctxt "constr-name" +msgid "pt-in-plane" +msgstr "" + +#: constraint.cpp:19 +msgctxt "constr-name" +msgid "pt-on-line" +msgstr "" + +#: constraint.cpp:20 +msgctxt "constr-name" +msgid "pt-on-face" +msgstr "" + +#: constraint.cpp:21 +msgctxt "constr-name" +msgid "eq-length" +msgstr "" + +#: constraint.cpp:22 +msgctxt "constr-name" +msgid "eq-length-and-pt-ln-dist" +msgstr "" + +#: constraint.cpp:23 +msgctxt "constr-name" +msgid "eq-pt-line-distances" +msgstr "" + +#: constraint.cpp:24 +msgctxt "constr-name" +msgid "length-ratio" +msgstr "" + +#: constraint.cpp:25 +msgctxt "constr-name" +msgid "length-difference" +msgstr "" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "symmetric" +msgstr "" + +#: constraint.cpp:27 +msgctxt "constr-name" +msgid "symmetric-h" +msgstr "" + +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "symmetric-v" +msgstr "" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "symmetric-line" +msgstr "" + +#: constraint.cpp:30 +msgctxt "constr-name" +msgid "at-midpoint" +msgstr "" + +#: constraint.cpp:31 +msgctxt "constr-name" +msgid "horizontal" +msgstr "" + +#: constraint.cpp:32 +msgctxt "constr-name" +msgid "vertical" +msgstr "" + +#: constraint.cpp:33 +msgctxt "constr-name" +msgid "diameter" +msgstr "" + +#: constraint.cpp:34 +msgctxt "constr-name" +msgid "pt-on-circle" +msgstr "" + +#: constraint.cpp:35 +msgctxt "constr-name" +msgid "same-orientation" +msgstr "" + +#: constraint.cpp:36 +msgctxt "constr-name" +msgid "angle" +msgstr "" + +#: constraint.cpp:37 +msgctxt "constr-name" +msgid "parallel" +msgstr "" + +#: constraint.cpp:38 +msgctxt "constr-name" +msgid "arc-line-tangent" +msgstr "" + +#: constraint.cpp:39 +msgctxt "constr-name" +msgid "cubic-line-tangent" +msgstr "" + +#: constraint.cpp:40 +msgctxt "constr-name" +msgid "curve-curve-tangent" +msgstr "" + +#: constraint.cpp:41 +msgctxt "constr-name" +msgid "perpendicular" +msgstr "" + +#: constraint.cpp:42 +msgctxt "constr-name" +msgid "eq-radius" +msgstr "" + +#: constraint.cpp:43 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "" + +#: constraint.cpp:44 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "" + +#: constraint.cpp:45 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "" + +#: constraint.cpp:46 +msgctxt "constr-name" +msgid "comment" +msgstr "" + +#: constraint.cpp:171 +msgid "" +"Bad selection for distance / diameter constraint. This constraint can apply " +"to:\n" +"\n" +" * two points (distance between points)\n" +" * a line segment (length)\n" +" * two points and a line segment or normal (projected distance)\n" +" * a workplane and a point (minimum distance)\n" +" * a line segment and a point (minimum distance)\n" +" * a plane face and a point (minimum distance)\n" +" * a circle or an arc (diameter)\n" +msgstr "" + +#: constraint.cpp:224 +msgid "" +"Bad selection for on point / curve / plane constraint. This constraint can " +"apply to:\n" +"\n" +" * two points (points coincident)\n" +" * a point and a workplane (point in plane)\n" +" * a point and a line segment (point on line)\n" +" * a point and a circle or arc (point on curve)\n" +" * a point and a plane face (point on face)\n" +msgstr "" + +#: constraint.cpp:286 +msgid "" +"Bad selection for equal length / radius constraint. This constraint can " +"apply to:\n" +"\n" +" * two line segments (equal length)\n" +" * two line segments and two points (equal point-line distances)\n" +" * a line segment and two points (equal point-line distances)\n" +" * a line segment, and a point and line segment (point-line distance " +"equals length)\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" +" * two circles or arcs (equal radius)\n" +" * a line segment and an arc (line segment length equals arc length)\n" +msgstr "" + +#: constraint.cpp:325 +msgid "" +"Bad selection for length ratio constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +msgstr "" + +#: constraint.cpp:342 +msgid "" +"Bad selection for length difference constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments\n" +msgstr "" + +#: constraint.cpp:368 +msgid "" +"Bad selection for at midpoint constraint. This constraint can apply to:\n" +"\n" +" * a line segment and a point (point at midpoint)\n" +" * a line segment and a workplane (line's midpoint on plane)\n" +msgstr "" + +#: constraint.cpp:426 +msgid "" +"Bad selection for symmetric constraint. This constraint can apply to:\n" +"\n" +" * two points or a line segment (symmetric about workplane's coordinate " +"axis)\n" +" * line segment, and two points or a line segment (symmetric about line " +"segment)\n" +" * workplane, and two points or a line segment (symmetric about " +"workplane)\n" +msgstr "" + +#: constraint.cpp:440 +msgid "" +"A workplane must be active when constraining symmetric without an explicit " +"symmetry plane." +msgstr "" + +#: constraint.cpp:470 +msgid "" +"Activate a workplane (with Sketch -> In Workplane) before applying a " +"horizontal or vertical constraint." +msgstr "" + +#: constraint.cpp:483 +msgid "" +"Bad selection for horizontal / vertical constraint. This constraint can " +"apply to:\n" +"\n" +" * two points\n" +" * a line segment\n" +msgstr "" + +#: constraint.cpp:504 +msgid "" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" +"\n" +" * two normals\n" +msgstr "" + +#: constraint.cpp:554 +msgid "Must select an angle constraint." +msgstr "" + +#: constraint.cpp:567 +msgid "Must select a constraint with associated label." +msgstr "" + +#: constraint.cpp:578 +msgid "" +"Bad selection for angle constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" + +#: constraint.cpp:635 +msgid "" +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent." +msgstr "" + +#: constraint.cpp:659 +msgid "" +"The tangent cubic and line segment must share an endpoint. Constrain them " +"with Constrain -> On Point before constraining tangent." +msgstr "" + +#: constraint.cpp:669 +msgid "Curve-curve tangency must apply in workplane." +msgstr "" + +#: constraint.cpp:687 +msgid "" +"The curves must share an endpoint. Constrain them with Constrain -> On Point " +"before constraining tangent." +msgstr "" + +#: constraint.cpp:696 +msgid "" +"Bad selection for parallel / tangent constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments (parallel)\n" +" * a line segment and a normal (parallel)\n" +" * two normals (parallel)\n" +" * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +msgstr "" + +#: constraint.cpp:714 +msgid "" +"Bad selection for perpendicular constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" + +#: constraint.cpp:729 +msgid "" +"Bad selection for lock point where dragged constraint. This constraint can " +"apply to:\n" +"\n" +" * a point\n" +msgstr "" + +#: constraint.cpp:740 +msgid "click center of comment text" +msgstr "" + +#: export.cpp:19 +msgid "" +"No solid model present; draw one with extrudes and revolves, or use Export " +"2d View to export bare lines and curves." +msgstr "" + +#: export.cpp:61 +msgid "" +"Bad selection for export section. Please select:\n" +"\n" +" * nothing, with an active workplane (workplane is section plane)\n" +" * a face (section plane through face)\n" +" * a point and two line segments (plane through point and parallel to " +"lines)\n" +msgstr "" + +#: export.cpp:822 +msgid "Active group mesh is empty; nothing to export." +msgstr "" + +#: exportvector.cpp:337 +msgid "freehand lines were replaced with continuous lines" +msgstr "" + +#: exportvector.cpp:339 +msgid "zigzag lines were replaced with continuous lines" +msgstr "" + +#: exportvector.cpp:593 +msgid "" +"Some aspects of the drawing have no DXF equivalent and were not exported:\n" +msgstr "" + +#: exportvector.cpp:839 +msgid "" +"PDF page size exceeds 200 by 200 inches; many viewers may reject this file." +msgstr "" + +#: file.cpp:44 group.cpp:91 +msgctxt "group-name" +msgid "sketch-in-plane" +msgstr "" + +#: file.cpp:62 +msgctxt "group-name" +msgid "#references" +msgstr "" + +#: file.cpp:549 +msgid "" +"Unrecognized data in file. This file may be corrupt, or from a newer version " +"of the program." +msgstr "" + +#: file.cpp:859 +msgctxt "title" +msgid "Missing File" +msgstr "" + +#: file.cpp:860 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "" + +#: file.cpp:862 +msgctxt "dialog" +msgid "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be " +"permanently removed." +msgstr "" + +#: file.cpp:865 +msgctxt "button" +msgid "&Yes" +msgstr "" + +#: file.cpp:867 +msgctxt "button" +msgid "&No" +msgstr "" + +#: file.cpp:869 +msgctxt "button" +msgid "&Cancel" +msgstr "" + +#: graphicswin.cpp:41 +msgid "&File" +msgstr "&Файл" + +#: graphicswin.cpp:42 +msgid "&New" +msgstr "&Новий" + +#: graphicswin.cpp:43 +msgid "&Open..." +msgstr "&Відкрити..." + +#: graphicswin.cpp:44 +msgid "Open &Recent" +msgstr "Відкрити &Недавні" + +#: graphicswin.cpp:45 +msgid "&Save" +msgstr "&Зберегти" + +#: graphicswin.cpp:46 +msgid "Save &As..." +msgstr "Зберегти &Як..." + +#: graphicswin.cpp:48 +msgid "Export &Image..." +msgstr "Експортувати &Зображення..." + +#: graphicswin.cpp:49 +msgid "Export 2d &View..." +msgstr "Експортувати 2d &Вигляд..." + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "Експортувати 2d &Секцію..." + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "Експортувати 3d &Скелет..." + +#: graphicswin.cpp:52 +msgid "Export Triangle &Mesh..." +msgstr "Експортувати Тригранний &Каркас..." + +#: graphicswin.cpp:53 +msgid "Export &Surfaces..." +msgstr "Експортувати &Поверхні..." + +#: graphicswin.cpp:54 +msgid "Im&port..." +msgstr "Ім&портувати..." + +#: graphicswin.cpp:57 +msgid "E&xit" +msgstr "В&ихід" + +#: graphicswin.cpp:60 +msgid "&Edit" +msgstr "&Редагувати" + +#: graphicswin.cpp:61 +msgid "&Undo" +msgstr "&Відмінити" + +#: graphicswin.cpp:62 +msgid "&Redo" +msgstr "&Повторити" + +#: graphicswin.cpp:63 +msgid "Re&generate All" +msgstr "Ре&генерувати Все" + +#: graphicswin.cpp:65 +msgid "Snap Selection to &Grid" +msgstr "Прикріпити Виділене до &Сітки" + +#: graphicswin.cpp:66 +msgid "Rotate Imported &90°" +msgstr "Обернути Імпортоване на &90°" + +#: graphicswin.cpp:68 +msgid "Cu&t" +msgstr "Ви&різати" + +#: graphicswin.cpp:69 +msgid "&Copy" +msgstr "&Копіювати" + +#: graphicswin.cpp:70 +msgid "&Paste" +msgstr "&Вставити" + +#: graphicswin.cpp:71 +msgid "Paste &Transformed..." +msgstr "Вставити &Трансфмованим..." + +#: graphicswin.cpp:72 +msgid "&Delete" +msgstr "&Delete" + +#: graphicswin.cpp:74 +msgid "Select &Edge Chain" +msgstr "Виділити Ланцуг &Ребер" + +#: graphicswin.cpp:75 +msgid "Select &All" +msgstr "Виділити &Усе" + +#: graphicswin.cpp:76 +msgid "&Unselect All" +msgstr "&Зняти Виділення з Усього" + +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "" + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "" + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "" + +#: graphicswin.cpp:84 +msgid "&View" +msgstr "&View" + +#: graphicswin.cpp:85 +msgid "Zoom &In" +msgstr "На&близити" + +#: graphicswin.cpp:86 +msgid "Zoom &Out" +msgstr "Від&далити" + +#: graphicswin.cpp:87 +msgid "Zoom To &Fit" +msgstr "Умістити на &Екрані" + +#: graphicswin.cpp:89 +msgid "Align View to &Workplane" +msgstr "Вирівняти Вигляд до Робочої &Площини" + +#: graphicswin.cpp:90 +msgid "Nearest &Ortho View" +msgstr "Найближчий &Ортогональний Вигляд" + +#: graphicswin.cpp:91 +msgid "Nearest &Isometric View" +msgstr "Найближчий &Ізометричний Вигляд" + +#: graphicswin.cpp:92 +msgid "&Center View At Point" +msgstr "&Центрувати Вигляд На Точці" + +#: graphicswin.cpp:94 +msgid "Show Snap &Grid" +msgstr "Показати &Сітку Прикріплення" + +#: graphicswin.cpp:95 +msgid "Use &Perspective Projection" +msgstr "Використовувати &Перспективну Проекцію" + +#: graphicswin.cpp:96 +msgid "Dimension &Units" +msgstr "" + +#: graphicswin.cpp:97 +msgid "Dimensions in &Millimeters" +msgstr "Розміри в &Міліметрах" + +#: graphicswin.cpp:98 +msgid "Dimensions in M&eters" +msgstr "" + +#: graphicswin.cpp:99 +msgid "Dimensions in &Inches" +msgstr "Розміри в &Дюймах" + +#: graphicswin.cpp:101 +msgid "Show &Toolbar" +msgstr "Показати Панель &Інструментів" + +#: graphicswin.cpp:102 +msgid "Show Property Bro&wser" +msgstr "Показати Вікно Власти&востей" + +#: graphicswin.cpp:104 +msgid "&Full Screen" +msgstr "&Повний Екран" + +#: graphicswin.cpp:106 +msgid "&New Group" +msgstr "&Нова Група" + +#: graphicswin.cpp:107 +msgid "Sketch In &3d" +msgstr "Креслення В &3d" + +#: graphicswin.cpp:108 +msgid "Sketch In New &Workplane" +msgstr "Креслення В Новій Робочій &Площині" + +#: graphicswin.cpp:110 +msgid "Step &Translating" +msgstr "Покрокове &Переміщення" + +#: graphicswin.cpp:111 +msgid "Step &Rotating" +msgstr "Покрокове &Обертання" + +#: graphicswin.cpp:113 +msgid "E&xtrude" +msgstr "Ви&давити" + +#: graphicswin.cpp:114 +msgid "&Helix" +msgstr "" + +#: graphicswin.cpp:115 +msgid "&Lathe" +msgstr "&Виточити" + +#: graphicswin.cpp:116 +msgid "Re&volve" +msgstr "" + +#: graphicswin.cpp:118 +msgid "Link / Assemble..." +msgstr "Приєднати / Монтувати..." + +#: graphicswin.cpp:119 +msgid "Link Recent" +msgstr "Приєднати Недавні" + +#: graphicswin.cpp:121 +msgid "&Sketch" +msgstr "&Креслення" + +#: graphicswin.cpp:122 +msgid "In &Workplane" +msgstr "В Робочій &Площині" + +#: graphicswin.cpp:123 +msgid "Anywhere In &3d" +msgstr "Будь-де В &3d" + +#: graphicswin.cpp:125 +msgid "Datum &Point" +msgstr "Опорна &Точка" + +#: graphicswin.cpp:126 +msgid "&Workplane" +msgstr "Робоча &Площина" + +#: graphicswin.cpp:128 +msgid "Line &Segment" +msgstr "&Відрізок Прямої" + +#: graphicswin.cpp:129 +msgid "C&onstruction Line Segment" +msgstr "Контсрук&ційний Відрізок Прямої" + +#: graphicswin.cpp:130 +msgid "&Rectangle" +msgstr "&Прямокутник" + +#: graphicswin.cpp:131 +msgid "&Circle" +msgstr "&Коло" + +#: graphicswin.cpp:132 +msgid "&Arc of a Circle" +msgstr "&Дуга Кола" + +#: graphicswin.cpp:133 +msgid "&Bezier Cubic Spline" +msgstr "Кубічний Сплайн &Без'є" + +#: graphicswin.cpp:135 +msgid "&Text in TrueType Font" +msgstr "&Текст з TrueType Шрифтом" + +#: graphicswin.cpp:136 +msgid "&Image" +msgstr "" + +#: graphicswin.cpp:138 +msgid "To&ggle Construction" +msgstr "Пере&мкнути Конструктивність" + +#: graphicswin.cpp:139 +msgid "Tangent &Arc at Point" +msgstr "Дотична &Дуга на Точці" + +#: graphicswin.cpp:140 +msgid "Split Curves at &Intersection" +msgstr "Розрізати Криві на &Перетині" + +#: graphicswin.cpp:142 +msgid "&Constrain" +msgstr "&Обмежити" + +#: graphicswin.cpp:143 +msgid "&Distance / Diameter" +msgstr "&Відстань / Діаметр" + +#: graphicswin.cpp:144 +msgid "Re&ference Dimension" +msgstr "Від&носний Розмір" + +#: graphicswin.cpp:145 +msgid "A&ngle" +msgstr "К&ут" + +#: graphicswin.cpp:146 +msgid "Reference An&gle" +msgstr "Відносний К&ут" + +#: graphicswin.cpp:147 +msgid "Other S&upplementary Angle" +msgstr "Інший Су&міжний Кут" + +#: graphicswin.cpp:148 +msgid "Toggle R&eference Dim" +msgstr "Перемкнути Від&носність Розмірів" + +#: graphicswin.cpp:150 +msgid "&Horizontal" +msgstr "&Горизонтально" + +#: graphicswin.cpp:151 +msgid "&Vertical" +msgstr "&Вертикально" + +#: graphicswin.cpp:153 +msgid "&On Point / Curve / Plane" +msgstr "&На точці / Кривій / Площині" + +#: graphicswin.cpp:154 +msgid "E&qual Length / Radius / Angle" +msgstr "Рі&вні Довжина / Радіус / Кут" + +#: graphicswin.cpp:155 +msgid "Length Ra&tio" +msgstr "Про&порція Довжин" + +#: graphicswin.cpp:156 +msgid "Length Diff&erence" +msgstr "Рі&зниця Довжин" + +#: graphicswin.cpp:157 +msgid "At &Midpoint" +msgstr "До &Середини" + +#: graphicswin.cpp:158 +msgid "S&ymmetric" +msgstr "Си&метрично" + +#: graphicswin.cpp:159 +msgid "Para&llel / Tangent" +msgstr "Пара&лельно / Дотична" + +#: graphicswin.cpp:160 +msgid "&Perpendicular" +msgstr "&Препендикулярно" + +#: graphicswin.cpp:161 +msgid "Same Orient&ation" +msgstr "Однакова Орієн&тація" + +#: graphicswin.cpp:162 +msgid "Lock Point Where &Dragged" +msgstr "Фіксувати Точку Після &Переміщення" + +#: graphicswin.cpp:164 +msgid "Comment" +msgstr "Коментар" + +#: graphicswin.cpp:166 +msgid "&Analyze" +msgstr "&Аналізувати" + +#: graphicswin.cpp:167 +msgid "Measure &Volume" +msgstr "Обрахувати &Об'єм" + +#: graphicswin.cpp:168 +msgid "Measure A&rea" +msgstr "Обрахувати Пл&ощу" + +#: graphicswin.cpp:169 +msgid "Measure &Perimeter" +msgstr "Обрахувати &Периметр" + +#: graphicswin.cpp:170 +msgid "Show &Interfering Parts" +msgstr "Показати &Дотичні Частини" + +#: graphicswin.cpp:171 +msgid "Show &Naked Edges" +msgstr "Показати &Приховані Ребра" + +#: graphicswin.cpp:172 +msgid "Show &Center of Mass" +msgstr "" + +#: graphicswin.cpp:174 +msgid "Show &Underconstrained Points" +msgstr "" + +#: graphicswin.cpp:176 +msgid "&Trace Point" +msgstr "&Трасувати Точку" + +#: graphicswin.cpp:177 +msgid "&Stop Tracing..." +msgstr "&Зупити Трасування..." + +#: graphicswin.cpp:178 +msgid "Step &Dimension..." +msgstr "Прорахувати &Розмір..." + +#: graphicswin.cpp:180 +msgid "&Help" +msgstr "&Довідка" + +#: graphicswin.cpp:181 +msgid "&Language" +msgstr "&Мова" + +#: graphicswin.cpp:182 +msgid "&Website / Manual" +msgstr "&Вебсайт / Посібник" + +#: graphicswin.cpp:184 +msgid "&About" +msgstr "&Про програму" + +#: graphicswin.cpp:352 +msgid "(no recent files)" +msgstr "" + +#: graphicswin.cpp:360 +#, c-format +msgid "File '%s' does not exist." +msgstr "" + +#: graphicswin.cpp:721 +msgid "No workplane is active, so the grid will not appear." +msgstr "" + +#: graphicswin.cpp:730 +msgid "" +"The perspective factor is set to zero, so the view will always be a parallel " +"projection.\n" +"\n" +"For a perspective projection, modify the perspective factor in the " +"configuration screen. A value around 0.3 is typical." +msgstr "" + +#: graphicswin.cpp:809 +msgid "" +"Select a point; this point will become the center of the view on screen." +msgstr "" + +#: graphicswin.cpp:1103 +msgid "No additional entities share endpoints with the selected entities." +msgstr "" + +#: graphicswin.cpp:1121 +msgid "" +"To use this command, select a point or other entity from an linked part, or " +"make a link group the active group." +msgstr "" + +#: graphicswin.cpp:1144 +msgid "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) " +"to define the plane for the snap grid." +msgstr "" + +#: graphicswin.cpp:1151 +msgid "" +"Can't snap these items to grid; select points, text comments, or constraints " +"with a label. To snap a line, select its endpoints." +msgstr "" + +#: graphicswin.cpp:1239 +msgid "No workplane selected. Activating default workplane for this group." +msgstr "" + +#: graphicswin.cpp:1242 +msgid "" +"No workplane is selected, and the active group does not have a default " +"workplane. Try selecting a workplane, or activating a sketch-in-new-" +"workplane group." +msgstr "" + +#: graphicswin.cpp:1263 +msgid "" +"Bad selection for tangent arc at point. Select a single point, or select " +"nothing to set up arc parameters." +msgstr "" + +#: graphicswin.cpp:1274 +msgid "click point on arc (draws anti-clockwise)" +msgstr "" + +#: graphicswin.cpp:1275 +msgid "click to place datum point" +msgstr "" + +#: graphicswin.cpp:1276 +msgid "click first point of line segment" +msgstr "" + +#: graphicswin.cpp:1278 +msgid "click first point of construction line segment" +msgstr "" + +#: graphicswin.cpp:1279 +msgid "click first point of cubic segment" +msgstr "" + +#: graphicswin.cpp:1280 +msgid "click center of circle" +msgstr "" + +#: graphicswin.cpp:1281 +msgid "click origin of workplane" +msgstr "" + +#: graphicswin.cpp:1282 +msgid "click one corner of rectangle" +msgstr "" + +#: graphicswin.cpp:1283 +msgid "click top left of text" +msgstr "" + +#: graphicswin.cpp:1289 +msgid "click top left of image" +msgstr "" + +#: graphicswin.cpp:1301 +msgid "" +"No entities are selected. Select entities before trying to toggle their " +"construction state." +msgstr "" + +#: group.cpp:86 +msgctxt "group-name" +msgid "sketch-in-3d" +msgstr "" + +#: group.cpp:142 +msgid "" +"Bad selection for new sketch in workplane. This group can be created with:\n" +"\n" +" * a point (through the point, orthogonal to coordinate axes)\n" +" * a point and two line segments (through the point, parallel to the " +"lines)\n" +" * a workplane (copy of the workplane)\n" +msgstr "" + +#: group.cpp:154 +msgid "" +"Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " +"will be extruded normal to the workplane." +msgstr "" + +#: group.cpp:163 +msgctxt "group-name" +msgid "extrude" +msgstr "" + +#: group.cpp:168 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:179 +msgid "" +"Bad selection for new lathe group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:189 +msgctxt "group-name" +msgid "lathe" +msgstr "" + +#: group.cpp:194 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:205 +msgid "" +"Bad selection for new revolve group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:217 +msgctxt "group-name" +msgid "revolve" +msgstr "" + +#: group.cpp:222 +msgid "Helix operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:233 +msgid "" +"Bad selection for new helix group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:245 +msgctxt "group-name" +msgid "helix" +msgstr "" + +#: group.cpp:258 +msgid "" +"Bad selection for new rotation. This group can be created with:\n" +"\n" +" * a point, while locked in workplane (rotate in plane, about that " +"point)\n" +" * a point and a line or a normal (rotate about an axis through the " +"point, and parallel to line / normal)\n" +msgstr "" + +#: group.cpp:271 +msgctxt "group-name" +msgid "rotate" +msgstr "" + +#: group.cpp:282 +msgctxt "group-name" +msgid "translate" +msgstr "" + +#: group.cpp:400 +msgid "(unnamed)" +msgstr "" + +#: groupmesh.cpp:708 +msgid "not closed contour, or not all same style!" +msgstr "" + +#: groupmesh.cpp:721 +msgid "points not all coplanar!" +msgstr "" + +#: groupmesh.cpp:723 +msgid "contour is self-intersecting!" +msgstr "" + +#: groupmesh.cpp:725 +msgid "zero-length edge!" +msgstr "" + +#: modify.cpp:254 +msgid "Must be sketching in workplane to create tangent arc." +msgstr "" + +#: modify.cpp:301 +msgid "" +"To create a tangent arc, select a point where two non-construction lines or " +"circles in this group and workplane join." +msgstr "" + +#: modify.cpp:388 +msgid "" +"Couldn't round this corner. Try a smaller radius, or try creating the " +"desired geometry by hand with tangency constraints." +msgstr "" + +#: modify.cpp:597 +msgid "Couldn't split this entity; lines, circles, or cubics only." +msgstr "" + +#: modify.cpp:624 +msgid "Must be sketching in workplane to split." +msgstr "" + +#: modify.cpp:631 +msgid "" +"Select two entities that intersect each other (e.g. two lines/circles/arcs " +"or a line/circle/arc and a point)." +msgstr "" + +#: modify.cpp:736 +msgid "Can't split; no intersection found." +msgstr "" + +#: mouse.cpp:560 +msgid "Assign to Style" +msgstr "Встановити Стиль" + +#: mouse.cpp:576 +msgid "No Style" +msgstr "Без Стилю" + +#: mouse.cpp:579 +msgid "Newly Created Custom Style..." +msgstr "Створити Користувацький Стиль..." + +#: mouse.cpp:586 +msgid "Group Info" +msgstr "Параметри Групи" + +#: mouse.cpp:606 +msgid "Style Info" +msgstr "Параметри Стилю" + +#: mouse.cpp:626 +msgid "Select Edge Chain" +msgstr "Виділити Ланцюг Ребер" + +#: mouse.cpp:632 +msgid "Toggle Reference Dimension" +msgstr "Перемкнути Відносність Розміру" + +#: mouse.cpp:638 +msgid "Other Supplementary Angle" +msgstr "Інший Суміжний Кут" + +#: mouse.cpp:643 +msgid "Snap to Grid" +msgstr "Прикріпити до Сітки" + +#: mouse.cpp:652 +msgid "Remove Spline Point" +msgstr "Видалити Точку Сплайну" + +#: mouse.cpp:687 +msgid "Add Spline Point" +msgstr "Додати Точку Сплайну" + +#: mouse.cpp:691 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "" + +#: mouse.cpp:716 +msgid "Toggle Construction" +msgstr "Пермкнути Конструктивність" + +#: mouse.cpp:731 +msgid "Delete Point-Coincident Constraint" +msgstr "Роз'єднати З'єднання Точок" + +#: mouse.cpp:750 +msgid "Cut" +msgstr "Вирізати" + +#: mouse.cpp:752 +msgid "Copy" +msgstr "Копіювати" + +#: mouse.cpp:756 +msgid "Select All" +msgstr "Виділити Усе" + +#: mouse.cpp:761 +msgid "Paste" +msgstr "Вставити" + +#: mouse.cpp:763 +msgid "Paste Transformed..." +msgstr "Вставити Трансформованим..." + +#: mouse.cpp:768 +msgid "Delete" +msgstr "Видалити" + +#: mouse.cpp:771 +msgid "Unselect All" +msgstr "Зняти Виділення з Усього" + +#: mouse.cpp:778 +msgid "Unselect Hovered" +msgstr "Зняти Виділення з Наведеного" + +#: mouse.cpp:787 +msgid "Zoom to Fit" +msgstr "Умістити на Екрані" + +#: mouse.cpp:990 mouse.cpp:1277 +msgid "click next point of line, or press Esc" +msgstr "" + +#: mouse.cpp:996 +msgid "" +"Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" + +#: mouse.cpp:1030 +msgid "click to place other corner of rectangle" +msgstr "" + +#: mouse.cpp:1050 +msgid "click to set radius" +msgstr "" + +#: mouse.cpp:1055 +msgid "" +"Can't draw arc in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" + +#: mouse.cpp:1074 +msgid "click to place point" +msgstr "" + +#: mouse.cpp:1090 +msgid "click next point of cubic, or press Esc" +msgstr "" + +#: mouse.cpp:1095 +msgid "" +"Sketching in a workplane already; sketch in 3d before creating new workplane." +msgstr "" + +#: mouse.cpp:1111 +msgid "" +"Can't draw text in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" + +#: mouse.cpp:1128 +msgid "click to place bottom right of text" +msgstr "" + +#: mouse.cpp:1134 +msgid "" +"Can't draw image in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "" + +#: mouse.cpp:1161 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "" + +#: platform/gui.cpp:85 platform/gui.cpp:89 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "" + +#: platform/gui.cpp:90 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "" + +#: platform/gui.cpp:94 +msgctxt "file-type" +msgid "PNG image" +msgstr "" + +#: platform/gui.cpp:98 +msgctxt "file-type" +msgid "STL mesh" +msgstr "" + +#: platform/gui.cpp:99 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Q3D Object file" +msgstr "" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "VRML text file" +msgstr "" + +#: platform/gui.cpp:107 platform/gui.cpp:114 platform/gui.cpp:121 +msgctxt "file-type" +msgid "STEP file" +msgstr "" + +#: platform/gui.cpp:111 +msgctxt "file-type" +msgid "PDF file" +msgstr "" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "" + +#: platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "" + +#: platform/gui.cpp:116 +msgctxt "file-type" +msgid "HPGL file" +msgstr "" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "G Code" +msgstr "" + +#: platform/gui.cpp:126 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "" + +#: platform/gui.cpp:130 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "" + +#: platform/guigtk.cpp:1317 platform/guimac.mm:1360 platform/guiwin.cpp:1608 +msgid "untitled" +msgstr "" + +#: platform/guigtk.cpp:1328 platform/guigtk.cpp:1361 platform/guimac.mm:1318 +#: platform/guiwin.cpp:1555 +msgctxt "title" +msgid "Save File" +msgstr "" + +#: platform/guigtk.cpp:1329 platform/guigtk.cpp:1362 platform/guimac.mm:1301 +#: platform/guiwin.cpp:1557 +msgctxt "title" +msgid "Open File" +msgstr "" + +#: platform/guigtk.cpp:1332 platform/guigtk.cpp:1368 +msgctxt "button" +msgid "_Cancel" +msgstr "" + +#: platform/guigtk.cpp:1333 platform/guigtk.cpp:1366 +msgctxt "button" +msgid "_Save" +msgstr "" + +#: platform/guigtk.cpp:1334 platform/guigtk.cpp:1367 +msgctxt "button" +msgid "_Open" +msgstr "" + +#: style.cpp:166 +msgid "" +"Can't assign style to an entity that's derived from another entity; try " +"assigning a style to this entity's parent." +msgstr "" + +#: style.cpp:665 +msgid "Style name cannot be empty" +msgstr "" + +#: textscreens.cpp:741 +msgid "Can't repeat fewer than 1 time." +msgstr "" + +#: textscreens.cpp:745 +msgid "Can't repeat more than 999 times." +msgstr "" + +#: textscreens.cpp:770 +msgid "Group name cannot be empty" +msgstr "" + +#: textscreens.cpp:813 +msgid "Opacity must be between zero and one." +msgstr "" + +#: textscreens.cpp:848 +msgid "Radius cannot be zero or negative." +msgstr "" + +#: toolbar.cpp:18 +msgid "Sketch line segment" +msgstr "Накреслети відрізок прямої" + +#: toolbar.cpp:20 +msgid "Sketch rectangle" +msgstr "Накреслити прямокутник" + +#: toolbar.cpp:22 +msgid "Sketch circle" +msgstr "Накреслити коло" + +#: toolbar.cpp:24 +msgid "Sketch arc of a circle" +msgstr "Накреслити дугу кола" + +#: toolbar.cpp:26 +msgid "Sketch curves from text in a TrueType font" +msgstr "Накреслити криві з тексту на TrueType шрифті" + +#: toolbar.cpp:28 +msgid "Sketch image from a file" +msgstr "" + +#: toolbar.cpp:30 +msgid "Create tangent arc at selected point" +msgstr "Створити дотичну дугу у вибраній точці" + +#: toolbar.cpp:32 +msgid "Sketch cubic Bezier spline" +msgstr "Накреслити кубічний сплайн Без'є" + +#: toolbar.cpp:34 +msgid "Sketch datum point" +msgstr "Накреслити опорну точку" + +#: toolbar.cpp:36 +msgid "Toggle construction" +msgstr "Перемкнути конструктивність" + +#: toolbar.cpp:38 +msgid "Split lines / curves where they intersect" +msgstr "Розрізати лініх / криві в місцях перетину" + +#: toolbar.cpp:42 +msgid "Constrain distance / diameter / length" +msgstr "Визначити відстань / діаметр / довжину" + +#: toolbar.cpp:44 +msgid "Constrain angle" +msgstr "Визначити кут" + +#: toolbar.cpp:46 +msgid "Constrain to be horizontal" +msgstr "Встановити горизонтально" + +#: toolbar.cpp:48 +msgid "Constrain to be vertical" +msgstr "Встановити перпендикулярно" + +#: toolbar.cpp:50 +msgid "Constrain to be parallel or tangent" +msgstr "Встановити паралельно або дотично" + +#: toolbar.cpp:52 +msgid "Constrain to be perpendicular" +msgstr "Встановити перпендикулярно" + +#: toolbar.cpp:54 +msgid "Constrain point on line / curve / plane / point" +msgstr "Прив'язати точку до лінії / кривої / площини / точки" + +#: toolbar.cpp:56 +msgid "Constrain symmetric" +msgstr "Встановити симетрично" + +#: toolbar.cpp:58 +msgid "Constrain equal length / radius / angle" +msgstr "Встановити однаковими відстань / радіус / кут" + +#: toolbar.cpp:60 +msgid "Constrain normals in same orientation" +msgstr "Спрямувати нормалі в одному напрямку" + +#: toolbar.cpp:62 +msgid "Other supplementary angle" +msgstr "Інший суміжний кут" + +#: toolbar.cpp:64 +msgid "Toggle reference dimension" +msgstr "Перемкнути відносність розміру" + +#: toolbar.cpp:68 +msgid "New group extruding active sketch" +msgstr "Нова група екструдування активного креслення" + +#: toolbar.cpp:70 +msgid "New group rotating active sketch" +msgstr "Нова група обертання актиного креслення" + +#: toolbar.cpp:72 +msgid "New group step and repeat rotating" +msgstr "Нова група крокування і повторення обертання" + +#: toolbar.cpp:74 +msgid "New group step and repeat translating" +msgstr "Нова група крокування і повторення зміщення" + +#: toolbar.cpp:76 +msgid "New group in new workplane (thru given entities)" +msgstr "Нова група в новій площині (через обрані об'екти)" + +#: toolbar.cpp:78 +msgid "New group in 3d" +msgstr "Нова група в 3d" + +#: toolbar.cpp:80 +msgid "New group linking / assembling file" +msgstr "Нова група приєднання / монтування файлу" + +#: toolbar.cpp:84 +msgid "Nearest isometric view" +msgstr "Найближчий ізометричний вигляд" + +#: toolbar.cpp:86 +msgid "Align view to active workplane" +msgstr "Вирівняти вигляд до активної робочої площини" + +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "" + +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "" + +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "" + +#: view.cpp:78 +msgid "Scale cannot be zero or negative." +msgstr "" + +#: view.cpp:90 view.cpp:99 +msgid "Bad format: specify x, y, z" +msgstr "" + +#~ msgid "Show Degrees of &Freedom" +#~ msgstr "Показати Степені &Свободи" + +#~ msgid "Show Menu &Bar" +#~ msgstr "Показати Панель &Меню" diff --git a/res/locales/zh_CN.po b/res/locales/zh_CN.po new file mode 100644 index 0000000..a2e1b45 --- /dev/null +++ b/res/locales/zh_CN.po @@ -0,0 +1,1778 @@ +# Chinese translations for SolveSpace package. +# Copyright (C) 2020 the PACKAGE authors +# This file is distributed under the same license as the SolveSpace package. +# , 2020. +# +msgid "" +msgstr "" +"Project-Id-Version: SolveSpace 3.0\n" +"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" +"POT-Creation-Date: 2020-11-17 20:50-0500\n" +"PO-Revision-Date: 2020-09-28 12:42+0800\n" +"Last-Translator: lomatus@163.com\n" +"Language-Team: none\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.4.1\n" + +#: clipboard.cpp:274 +msgid "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." +msgstr "" +"仅在工作平面中剪切、粘贴和复制工作。\n" +"\n" +"使用\"工作平面中的草图 -+\"激活一个。" + +#: clipboard.cpp:291 +msgid "Clipboard is empty; nothing to paste." +msgstr "剪贴板为空;没有要粘贴的内容。" + +#: clipboard.cpp:338 +msgid "Number of copies to paste must be at least one." +msgstr "要粘贴的副本数必须至少为 1 个。" + +#: clipboard.cpp:354 textscreens.cpp:783 +msgid "Scale cannot be zero." +msgstr "缩放不能为零。" + +#: clipboard.cpp:396 +msgid "Select one point to define origin of rotation." +msgstr "选择一个点以定义旋转原点。" + +#: clipboard.cpp:408 +msgid "Select two points to define translation vector." +msgstr "选择两个点来定义转换向量。" + +#: clipboard.cpp:418 +msgid "" +"Transformation is identity. So all copies will be exactly on top of each " +"other." +msgstr "转换就是标识,因此所有的复制在彼此之上。" + +#: clipboard.cpp:422 +msgid "Too many items to paste; split this into smaller pastes." +msgstr "要粘贴的项目太多; 请把他们拆分。" + +#: clipboard.cpp:427 +msgid "No workplane active." +msgstr "没有工作平面处于活动状态。" + +#: confscreen.cpp:410 +msgid "Bad format: specify coordinates as x, y, z" +msgstr "格式错误:将坐标指定为 x、y、z" + +#: confscreen.cpp:420 style.cpp:659 textscreens.cpp:805 +msgid "Bad format: specify color as r, g, b" +msgstr "格式错误:将颜色指定为 r、g、b" + +#: confscreen.cpp:446 +msgid "" +"The perspective factor will have no effect until you enable View -> Use " +"Perspective Projection." +msgstr "在启用\"视图 -= 使用透视投影\"之前,透视因子将不起作用。" + +#: confscreen.cpp:459 confscreen.cpp:469 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "在十进制之后指定 0 和 %d 数字之间。" + +#: confscreen.cpp:481 +msgid "Export scale must not be zero!" +msgstr "输出比例不能为零!" + +#: confscreen.cpp:493 +msgid "Cutter radius offset must not be negative!" +msgstr "刀具半径偏移不能为负数!" + +#: confscreen.cpp:547 +msgid "Bad value: autosave interval should be positive" +msgstr "坏值:自动保存间隔应为正" + +#: confscreen.cpp:550 +msgid "Bad format: specify interval in integral minutes" +msgstr "格式错误:以整数分钟为单位指定间隔" + +#: constraint.cpp:12 +msgctxt "constr-name" +msgid "pts-coincident" +msgstr "点约束" + +#: constraint.cpp:13 +msgctxt "constr-name" +msgid "pt-pt-distance" +msgstr "点点间距" + +#: constraint.cpp:14 +msgctxt "constr-name" +msgid "pt-line-distance" +msgstr "线长" + +#: constraint.cpp:15 +msgctxt "constr-name" +msgid "pt-plane-distance" +msgstr "平面具体" + +#: constraint.cpp:16 +msgctxt "constr-name" +msgid "pt-face-distance" +msgstr "面间距" + +#: constraint.cpp:17 +msgctxt "constr-name" +msgid "proj-pt-pt-distance" +msgstr "点点距离" + +#: constraint.cpp:18 +msgctxt "constr-name" +msgid "pt-in-plane" +msgstr "点在面" + +#: constraint.cpp:19 +msgctxt "constr-name" +msgid "pt-on-line" +msgstr "点在线" + +#: constraint.cpp:20 +msgctxt "constr-name" +msgid "pt-on-face" +msgstr "点在面" + +#: constraint.cpp:21 +msgctxt "constr-name" +msgid "eq-length" +msgstr "长度相同" + +#: constraint.cpp:22 +msgctxt "constr-name" +msgid "eq-length-and-pt-ln-dist" +msgstr "长度相等且点在距离" + +#: constraint.cpp:23 +msgctxt "constr-name" +msgid "eq-pt-line-distances" +msgstr "点线距离相等" + +#: constraint.cpp:24 +msgctxt "constr-name" +msgid "length-ratio" +msgstr "长度比率" + +#: constraint.cpp:25 +msgctxt "constr-name" +msgid "length-difference" +msgstr "长度不同" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "symmetric" +msgstr "对称的" + +#: constraint.cpp:27 +msgctxt "constr-name" +msgid "symmetric-h" +msgstr "水平对称" + +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "symmetric-v" +msgstr "纵向对称" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "symmetric-line" +msgstr "线对称" + +#: constraint.cpp:30 +msgctxt "constr-name" +msgid "at-midpoint" +msgstr "在中点" + +#: constraint.cpp:31 +msgctxt "constr-name" +msgid "horizontal" +msgstr "水平约束" + +#: constraint.cpp:32 +msgctxt "constr-name" +msgid "vertical" +msgstr "垂直约束" + +#: constraint.cpp:33 +msgctxt "constr-name" +msgid "diameter" +msgstr "直径约束" + +#: constraint.cpp:34 +msgctxt "constr-name" +msgid "pt-on-circle" +msgstr "圆点约束" + +#: constraint.cpp:35 +msgctxt "constr-name" +msgid "same-orientation" +msgstr "相同原点" + +#: constraint.cpp:36 +msgctxt "constr-name" +msgid "angle" +msgstr "角度约束" + +#: constraint.cpp:37 +msgctxt "constr-name" +msgid "parallel" +msgstr "平行约束" + +#: constraint.cpp:38 +msgctxt "constr-name" +msgid "arc-line-tangent" +msgstr "弧切线" + +#: constraint.cpp:39 +msgctxt "constr-name" +msgid "cubic-line-tangent" +msgstr "立方体切线" + +#: constraint.cpp:40 +msgctxt "constr-name" +msgid "curve-curve-tangent" +msgstr "曲线间切线" + +#: constraint.cpp:41 +msgctxt "constr-name" +msgid "perpendicular" +msgstr "垂直约束" + +#: constraint.cpp:42 +msgctxt "constr-name" +msgid "eq-radius" +msgstr "等于半径" + +#: constraint.cpp:43 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "等于角度" + +#: constraint.cpp:44 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "等于线长或弧长" + +#: constraint.cpp:45 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "锁定位置" + +#: constraint.cpp:46 +msgctxt "constr-name" +msgid "comment" +msgstr "备注" + +#: constraint.cpp:171 +msgid "" +"Bad selection for distance / diameter constraint. This constraint can apply " +"to:\n" +"\n" +" * two points (distance between points)\n" +" * a line segment (length)\n" +" * two points and a line segment or normal (projected distance)\n" +" * a workplane and a point (minimum distance)\n" +" * a line segment and a point (minimum distance)\n" +" * a plane face and a point (minimum distance)\n" +" * a circle or an arc (diameter)\n" +msgstr "" +"距离/直径约束的选择错误。此约束可应用于:\n" +"\n" +"* 两点(点之间的距离)\n" +" * 线段(长度)\n" +" * 两个点和线段或法线(投影距离)\n" +" * 工作平面和点(最小距离)\n" +" * 线段和点(最小距离)\n" +" * 平面面和点(最小距离)\n" +" * 圆或弧(直径)\n" + +#: constraint.cpp:224 +msgid "" +"Bad selection for on point / curve / plane constraint. This constraint can " +"apply to:\n" +"\n" +" * two points (points coincident)\n" +" * a point and a workplane (point in plane)\n" +" * a point and a line segment (point on line)\n" +" * a point and a circle or arc (point on curve)\n" +" * a point and a plane face (point on face)\n" +msgstr "" +"点 / 曲线 / 平面约束的选定方法错误。此约束可应用于:\n" +"\n" +"* 两点(点重合)\n" +" * 一个点和一个工作平面(平面中点)\n" +" * 点和线段(点在线)\n" +" * 一个点和一个圆或圆(曲线上的点)\n" +" * 点和平面面(点在脸上)\n" + +#: constraint.cpp:286 +msgid "" +"Bad selection for equal length / radius constraint. This constraint can " +"apply to:\n" +"\n" +" * two line segments (equal length)\n" +" * two line segments and two points (equal point-line distances)\n" +" * a line segment and two points (equal point-line distances)\n" +" * a line segment, and a point and line segment (point-line distance " +"equals length)\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" +" * two circles or arcs (equal radius)\n" +" * a line segment and an arc (line segment length equals arc length)\n" +msgstr "" +"等长度/半径约束的选定方法错误。此约束可应用于:\n" +"\n" +"* 两个线段(相等长度)\n" +" * 两个线段和两个点(相等的点线距离)\n" +" * 线段和两个点(相等的点线距离)\n" +" * 线段和点段和线段(点线距离等于长度)\n" +" * 四条线段或法线(A、B 和 C、D 之间的等角)\n" +" * 三条线段或法线(A、B 和 B、C 之间的等角)\n" +" * 两个圆或圆(相等半径)\n" +" * 线段和圆弧(线段长度等于弧长)\n" + +#: constraint.cpp:325 +msgid "" +"Bad selection for length ratio constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +msgstr "" +"长度比率约束的选择错误。此约束可应用于:\n" +"\n" +"* 两个线段\n" + +#: constraint.cpp:342 +msgid "" +"Bad selection for length difference constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments\n" +msgstr "" +"长度差异约束的选择错误。此约束可应用于:\n" +"\n" +"* 两个线段\n" + +#: constraint.cpp:368 +msgid "" +"Bad selection for at midpoint constraint. This constraint can apply to:\n" +"\n" +" * a line segment and a point (point at midpoint)\n" +" * a line segment and a workplane (line's midpoint on plane)\n" +msgstr "" +"中点约束的选定方法错误。此约束可应用于:\n" +"\n" +"* 线段和点(点在中点)\n" +" * 线段和工作平面(平面上的线中点)\n" + +#: constraint.cpp:426 +msgid "" +"Bad selection for symmetric constraint. This constraint can apply to:\n" +"\n" +" * two points or a line segment (symmetric about workplane's coordinate " +"axis)\n" +" * line segment, and two points or a line segment (symmetric about line " +"segment)\n" +" * workplane, and two points or a line segment (symmetric about " +"workplane)\n" +msgstr "" +"对称约束的选择错误。此约束可应用于:\n" +"\n" +"* 两个点或线段(与工作平面的坐标轴对称)\n" +" * 线段,和两个点或线段(对称的线段)\n" +" * 工作平面和两个点或线段(工作平面对称)\n" + +#: constraint.cpp:440 +msgid "" +"A workplane must be active when constraining symmetric without an explicit " +"symmetry plane." +msgstr "在没有显式对称平面约束对称时,工作平面必须处于活动状态。" + +#: constraint.cpp:470 +msgid "" +"Activate a workplane (with Sketch -> In Workplane) before applying a " +"horizontal or vertical constraint." +msgstr "在应用水平或垂直约束之前,激活工作平面(使用草图 -= 在工作平面中)。" + +#: constraint.cpp:483 +msgid "" +"Bad selection for horizontal / vertical constraint. This constraint can " +"apply to:\n" +"\n" +" * two points\n" +" * a line segment\n" +msgstr "" +"水平/垂直约束的选择错误。此约束可应用于:\n" +"\n" +"• 两点\n" +" • 线段\n" + +#: constraint.cpp:504 +msgid "" +"Bad selection for same orientation constraint. This constraint can apply " +"to:\n" +"\n" +" * two normals\n" +msgstr "" +"同一方向约束的选择错误。此约束可应用于:\n" +"\n" +"• 两个法线\n" + +#: constraint.cpp:554 +msgid "Must select an angle constraint." +msgstr "必须选择角度约束。" + +#: constraint.cpp:567 +msgid "Must select a constraint with associated label." +msgstr "必须选择具有关联标签的约束。" + +#: constraint.cpp:578 +msgid "" +"Bad selection for angle constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"角度约束的选择错误。此约束可应用于:\n" +"\n" +"* 两个线段\n" +" * 线段和法线\n" +" • 两个法线\n" + +#: constraint.cpp:635 +msgid "" +"The tangent arc and line segment must share an endpoint. Constrain them with " +"Constrain -> On Point before constraining tangent." +msgstr "切线弧和线段必须共享一个端点。在约束切线之前,使用约束 -= 点约束它们。" + +#: constraint.cpp:659 +msgid "" +"The tangent cubic and line segment must share an endpoint. Constrain them " +"with Constrain -> On Point before constraining tangent." +msgstr "" +"切线立方段和线段必须共享终结点。在约束切线之前,使用约束 -= 点约束它们。" + +#: constraint.cpp:669 +msgid "Curve-curve tangency must apply in workplane." +msgstr "曲线曲线切线必须应用于工作平面。" + +#: constraint.cpp:687 +msgid "" +"The curves must share an endpoint. Constrain them with Constrain -> On Point " +"before constraining tangent." +msgstr "曲线必须共享一个终结点。在约束切线之前,使用约束 -= 点约束它们。" + +#: constraint.cpp:696 +msgid "" +"Bad selection for parallel / tangent constraint. This constraint can apply " +"to:\n" +"\n" +" * two line segments (parallel)\n" +" * a line segment and a normal (parallel)\n" +" * two normals (parallel)\n" +" * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +msgstr "" +"平行/切线约束的选择错误。此约束可应用于:\n" +"\n" +"* 两条线段(平行)\n" +" * 线段和法线(平行)\n" +" * 两个法线(平行)\n" +" * 共享端点的两条线段、弧线或贝塞尔(切线)\n" + +#: constraint.cpp:714 +msgid "" +"Bad selection for perpendicular constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" +"垂直约束的选择错误。此约束可应用于:\n" +"\n" +"* 两个线段\n" +" * 线段和法线\n" +" • 两个法线\n" + +#: constraint.cpp:729 +msgid "" +"Bad selection for lock point where dragged constraint. This constraint can " +"apply to:\n" +"\n" +" * a point\n" +msgstr "" +"拖动约束的锁点选择错误。此约束可应用于:\n" +"\n" +"• 一点\n" + +#: constraint.cpp:740 +msgid "click center of comment text" +msgstr "单击注释文本的中心" + +#: export.cpp:19 +msgid "" +"No solid model present; draw one with extrudes and revolves, or use Export " +"2d View to export bare lines and curves." +msgstr "" +"不存在实体模型;使用拉伸和旋转绘制一个,或使用导出 2d 视图导出裸线和曲线。" + +#: export.cpp:61 +msgid "" +"Bad selection for export section. Please select:\n" +"\n" +" * nothing, with an active workplane (workplane is section plane)\n" +" * a face (section plane through face)\n" +" * a point and two line segments (plane through point and parallel to " +"lines)\n" +msgstr "" +"导出部分的选择错误。请选择:\n" +"\n" +"* 无,带活动工作平面(工作平面为剖面平面)\n" +" * 脸(通过面的剖面)\n" +" * 一个点和两个线段(平面穿过点和平行线)\n" + +#: export.cpp:822 +msgid "Active group mesh is empty; nothing to export." +msgstr "活动组网格为空;没有要导出的。" + +#: exportvector.cpp:337 +msgid "freehand lines were replaced with continuous lines" +msgstr "徒手线替换为连续线" + +#: exportvector.cpp:339 +msgid "zigzag lines were replaced with continuous lines" +msgstr "锯齿线替换为连续线" + +#: exportvector.cpp:593 +msgid "" +"Some aspects of the drawing have no DXF equivalent and were not exported:\n" +msgstr "绘图的某些方面没有 DXF 等效项,并且未导出:\n" + +#: exportvector.cpp:839 +msgid "" +"PDF page size exceeds 200 by 200 inches; many viewers may reject this file." +msgstr "PDF 页面大小超过 200 英寸或 200 英寸;许多查看器可能会拒绝此文件。" + +#: file.cpp:44 group.cpp:91 +msgctxt "group-name" +msgid "sketch-in-plane" +msgstr "平面草图" + +#: file.cpp:62 +msgctxt "group-name" +msgid "#references" +msgstr "#参考" + +#: file.cpp:549 +msgid "" +"Unrecognized data in file. This file may be corrupt, or from a newer version " +"of the program." +msgstr "未识别文件内数据。该文件可能损坏,或使用新版本应用程序尝试打开。" + +#: file.cpp:859 +msgctxt "title" +msgid "Missing File" +msgstr "文件丢失" + +#: file.cpp:860 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "连接的文件不存在:\"%s\"。" + +#: file.cpp:862 +msgctxt "dialog" +msgid "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be " +"permanently removed." +msgstr "您是否想手工查找?如果是,所有基于该丢失文件的几何对象将会被全部删除." + +#: file.cpp:865 +msgctxt "button" +msgid "&Yes" +msgstr "是(&Y)" + +#: file.cpp:867 +msgctxt "button" +msgid "&No" +msgstr "否(&N)" + +#: file.cpp:869 +msgctxt "button" +msgid "&Cancel" +msgstr "取消(&C)" + +#: graphicswin.cpp:41 +msgid "&File" +msgstr "文件(&F)" + +#: graphicswin.cpp:42 +msgid "&New" +msgstr "新建(&N)" + +#: graphicswin.cpp:43 +msgid "&Open..." +msgstr "打开(&O)" + +#: graphicswin.cpp:44 +msgid "Open &Recent" +msgstr "打开最近使用(&R)" + +#: graphicswin.cpp:45 +msgid "&Save" +msgstr "保存(&S)" + +#: graphicswin.cpp:46 +msgid "Save &As..." +msgstr "另存为(&A)" + +#: graphicswin.cpp:48 +msgid "Export &Image..." +msgstr "导出图片(&I)" + +#: graphicswin.cpp:49 +msgid "Export 2d &View..." +msgstr "导出2D视图(&V)" + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "导出2D截面(&S)" + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "导出3D线框模型(&W)" + +#: graphicswin.cpp:52 +msgid "Export Triangle &Mesh..." +msgstr "导出三角面模型(&M)" + +#: graphicswin.cpp:53 +msgid "Export &Surfaces..." +msgstr "导出表面模型(&S)" + +#: graphicswin.cpp:54 +msgid "Im&port..." +msgstr "导入(&P)" + +#: graphicswin.cpp:57 +msgid "E&xit" +msgstr "退出(&E)" + +#: graphicswin.cpp:60 +msgid "&Edit" +msgstr "编辑(&E)" + +#: graphicswin.cpp:61 +msgid "&Undo" +msgstr "回退(&U)" + +#: graphicswin.cpp:62 +msgid "&Redo" +msgstr "重做(&R)" + +#: graphicswin.cpp:63 +msgid "Re&generate All" +msgstr "重新生成所有(&G)" + +#: graphicswin.cpp:65 +msgid "Snap Selection to &Grid" +msgstr "选择到轴线(&G)" + +#: graphicswin.cpp:66 +msgid "Rotate Imported &90°" +msgstr "旋转导入模型90° (&9)" + +#: graphicswin.cpp:68 +msgid "Cu&t" +msgstr "剪切 (&T)" + +#: graphicswin.cpp:69 +msgid "&Copy" +msgstr "复制 (&C)" + +#: graphicswin.cpp:70 +msgid "&Paste" +msgstr "粘贴 (&P)" + +#: graphicswin.cpp:71 +msgid "Paste &Transformed..." +msgstr "粘贴移动(&T)" + +#: graphicswin.cpp:72 +msgid "&Delete" +msgstr "删除(&D)" + +#: graphicswin.cpp:74 +msgid "Select &Edge Chain" +msgstr "选择边缘约束(&E)" + +#: graphicswin.cpp:75 +msgid "Select &All" +msgstr "选择所有(&A)" + +#: graphicswin.cpp:76 +msgid "&Unselect All" +msgstr "取消全选(&U)" + +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "线型(&L)" + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "查看投影...(&V)" + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "配置 (&F)" + +#: graphicswin.cpp:84 +msgid "&View" +msgstr "查看 (&V)" + +#: graphicswin.cpp:85 +msgid "Zoom &In" +msgstr "放大(&I)" + +#: graphicswin.cpp:86 +msgid "Zoom &Out" +msgstr "缩小(&O)" + +#: graphicswin.cpp:87 +msgid "Zoom To &Fit" +msgstr "自动缩放(&F)" + +#: graphicswin.cpp:89 +msgid "Align View to &Workplane" +msgstr "切换视图至平面(&W)" + +#: graphicswin.cpp:90 +msgid "Nearest &Ortho View" +msgstr "Ortho视图 (&O)" + +#: graphicswin.cpp:91 +msgid "Nearest &Isometric View" +msgstr "ISO视图 (&I)" + +#: graphicswin.cpp:92 +msgid "&Center View At Point" +msgstr "以点为中心视图 (&C)" + +#: graphicswin.cpp:94 +msgid "Show Snap &Grid" +msgstr "显示捕捉轴线 (&G)" + +#: graphicswin.cpp:95 +msgid "Use &Perspective Projection" +msgstr "使用远景透视(&P)" + +#: graphicswin.cpp:96 +msgid "Dimension &Units" +msgstr "标注单位(&U)" + +#: graphicswin.cpp:97 +msgid "Dimensions in &Millimeters" +msgstr "标注单位 mm (&M)" + +#: graphicswin.cpp:98 +msgid "Dimensions in M&eters" +msgstr "标注单位m (&E)" + +#: graphicswin.cpp:99 +msgid "Dimensions in &Inches" +msgstr "标准单位英寸 (&I)" + +#: graphicswin.cpp:101 +msgid "Show &Toolbar" +msgstr "显示工具条(&T)" + +#: graphicswin.cpp:102 +msgid "Show Property Bro&wser" +msgstr "显示属性浏览器(&W)" + +#: graphicswin.cpp:104 +msgid "&Full Screen" +msgstr "全屏(&F)" + +#: graphicswin.cpp:106 +msgid "&New Group" +msgstr "新组合(&N)" + +#: graphicswin.cpp:107 +msgid "Sketch In &3d" +msgstr "在三维内绘制(&3)" + +#: graphicswin.cpp:108 +msgid "Sketch In New &Workplane" +msgstr "在新工作面绘制(&W)" + +#: graphicswin.cpp:110 +msgid "Step &Translating" +msgstr "移动(&T)" + +#: graphicswin.cpp:111 +msgid "Step &Rotating" +msgstr "旋转(&R)" + +#: graphicswin.cpp:113 +msgid "E&xtrude" +msgstr "挤出(&E)" + +#: graphicswin.cpp:114 +msgid "&Helix" +msgstr "螺旋(&H)" + +#: graphicswin.cpp:115 +msgid "&Lathe" +msgstr "扫略(&L)" + +#: graphicswin.cpp:116 +msgid "Re&volve" +msgstr "旋转(&V)" + +#: graphicswin.cpp:118 +msgid "Link / Assemble..." +msgstr "链接/装配..." + +#: graphicswin.cpp:119 +msgid "Link Recent" +msgstr "连接最近文件" + +#: graphicswin.cpp:121 +msgid "&Sketch" +msgstr "绘图(&S)" + +#: graphicswin.cpp:122 +msgid "In &Workplane" +msgstr "在工作平面(&W)" + +#: graphicswin.cpp:123 +msgid "Anywhere In &3d" +msgstr "在3D的任何位置(&3)" + +#: graphicswin.cpp:125 +msgid "Datum &Point" +msgstr "基准点(&P)" + +#: graphicswin.cpp:126 +msgid "&Workplane" +msgstr "工作面(&W)" + +#: graphicswin.cpp:128 +msgid "Line &Segment" +msgstr "线段(&S)" + +#: graphicswin.cpp:129 +msgid "C&onstruction Line Segment" +msgstr "构造线段(&C)" + +#: graphicswin.cpp:130 +msgid "&Rectangle" +msgstr "矩形(&R)" + +#: graphicswin.cpp:131 +msgid "&Circle" +msgstr "圆线(&C)" + +#: graphicswin.cpp:132 +msgid "&Arc of a Circle" +msgstr "圆弧(&A)" + +#: graphicswin.cpp:133 +msgid "&Bezier Cubic Spline" +msgstr "立方体线的贝塞尔曲线(&B)" + +#: graphicswin.cpp:135 +msgid "&Text in TrueType Font" +msgstr "TrueTyoe字体文字(&T)" + +#: graphicswin.cpp:136 +msgid "&Image" +msgstr "图片(&I)" + +#: graphicswin.cpp:138 +msgid "To&ggle Construction" +msgstr "切换构造(&G)" + +#: graphicswin.cpp:139 +msgid "Tangent &Arc at Point" +msgstr "弧线切线点(&A)" + +#: graphicswin.cpp:140 +msgid "Split Curves at &Intersection" +msgstr "在交叉处拆分曲线(&I)" + +#: graphicswin.cpp:142 +msgid "&Constrain" +msgstr "约束(&C)" + +#: graphicswin.cpp:143 +msgid "&Distance / Diameter" +msgstr "距离/直径(&D)" + +#: graphicswin.cpp:144 +msgid "Re&ference Dimension" +msgstr "参考标注(&F)" + +#: graphicswin.cpp:145 +msgid "A&ngle" +msgstr "角度(&A)" + +#: graphicswin.cpp:146 +msgid "Reference An&gle" +msgstr "参考角度(&G)" + +#: graphicswin.cpp:147 +msgid "Other S&upplementary Angle" +msgstr "其它增补角度(&U)" + +#: graphicswin.cpp:148 +msgid "Toggle R&eference Dim" +msgstr "切换参考标注(&E)" + +#: graphicswin.cpp:150 +msgid "&Horizontal" +msgstr "水平约束(&H)" + +#: graphicswin.cpp:151 +msgid "&Vertical" +msgstr "垂直约束(&V)" + +#: graphicswin.cpp:153 +msgid "&On Point / Curve / Plane" +msgstr "在点线面(&O)" + +#: graphicswin.cpp:154 +msgid "E&qual Length / Radius / Angle" +msgstr "等于/长度/半径/角度(&Q)" + +#: graphicswin.cpp:155 +msgid "Length Ra&tio" +msgstr "长度比例(&T)" + +#: graphicswin.cpp:156 +msgid "Length Diff&erence" +msgstr "长度偏差(&E)" + +#: graphicswin.cpp:157 +msgid "At &Midpoint" +msgstr "在中点(&M)" + +#: graphicswin.cpp:158 +msgid "S&ymmetric" +msgstr "对称(&Y)" + +#: graphicswin.cpp:159 +msgid "Para&llel / Tangent" +msgstr "水平/切线(&L)" + +#: graphicswin.cpp:160 +msgid "&Perpendicular" +msgstr "垂直的(&P)" + +#: graphicswin.cpp:161 +msgid "Same Orient&ation" +msgstr "相同方向(&A)" + +#: graphicswin.cpp:162 +msgid "Lock Point Where &Dragged" +msgstr "锁定点位置(&D)" + +#: graphicswin.cpp:164 +msgid "Comment" +msgstr "备注" + +#: graphicswin.cpp:166 +msgid "&Analyze" +msgstr "分析(&A)" + +#: graphicswin.cpp:167 +msgid "Measure &Volume" +msgstr "测量体积(&V)" + +#: graphicswin.cpp:168 +msgid "Measure A&rea" +msgstr "测量面积(&R)" + +#: graphicswin.cpp:169 +msgid "Measure &Perimeter" +msgstr "测量周长(&P)" + +#: graphicswin.cpp:170 +msgid "Show &Interfering Parts" +msgstr "显示干涉零件(&I)" + +#: graphicswin.cpp:171 +msgid "Show &Naked Edges" +msgstr "显示孤立边(&N)" + +#: graphicswin.cpp:172 +msgid "Show &Center of Mass" +msgstr "显示中心(&C)" + +#: graphicswin.cpp:174 +msgid "Show &Underconstrained Points" +msgstr "显示无效约束点(&U)" + +#: graphicswin.cpp:176 +msgid "&Trace Point" +msgstr "跟踪点(&T)" + +#: graphicswin.cpp:177 +msgid "&Stop Tracing..." +msgstr "停止跟踪(&S)" + +#: graphicswin.cpp:178 +msgid "Step &Dimension..." +msgstr "逐步标注(&D)" + +#: graphicswin.cpp:180 +msgid "&Help" +msgstr "帮助(&H)" + +#: graphicswin.cpp:181 +msgid "&Language" +msgstr "语言(&L)" + +#: graphicswin.cpp:182 +msgid "&Website / Manual" +msgstr "网页/手册(&W)" + +#: graphicswin.cpp:184 +msgid "&About" +msgstr "关于(&A)" + +#: graphicswin.cpp:352 +msgid "(no recent files)" +msgstr "(无文件)" + +#: graphicswin.cpp:360 +#, c-format +msgid "File '%s' does not exist." +msgstr "文件不存在: \"%s\"。" + +#: graphicswin.cpp:721 +msgid "No workplane is active, so the grid will not appear." +msgstr "没有激活的工作面,因此无法显示轴网。" + +#: graphicswin.cpp:730 +msgid "" +"The perspective factor is set to zero, so the view will always be a parallel " +"projection.\n" +"\n" +"For a perspective projection, modify the perspective factor in the " +"configuration screen. A value around 0.3 is typical." +msgstr "" + +#: graphicswin.cpp:809 +msgid "" +"Select a point; this point will become the center of the view on screen." +msgstr "" + +#: graphicswin.cpp:1103 +msgid "No additional entities share endpoints with the selected entities." +msgstr "" + +#: graphicswin.cpp:1121 +msgid "" +"To use this command, select a point or other entity from an linked part, or " +"make a link group the active group." +msgstr "" + +#: graphicswin.cpp:1144 +msgid "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) " +"to define the plane for the snap grid." +msgstr "" + +#: graphicswin.cpp:1151 +msgid "" +"Can't snap these items to grid; select points, text comments, or constraints " +"with a label. To snap a line, select its endpoints." +msgstr "" + +#: graphicswin.cpp:1239 +msgid "No workplane selected. Activating default workplane for this group." +msgstr "" + +#: graphicswin.cpp:1242 +msgid "" +"No workplane is selected, and the active group does not have a default " +"workplane. Try selecting a workplane, or activating a sketch-in-new-" +"workplane group." +msgstr "" + +#: graphicswin.cpp:1263 +msgid "" +"Bad selection for tangent arc at point. Select a single point, or select " +"nothing to set up arc parameters." +msgstr "" + +#: graphicswin.cpp:1274 +msgid "click point on arc (draws anti-clockwise)" +msgstr "点击弧线的点(逆时针方向绘制)" + +#: graphicswin.cpp:1275 +msgid "click to place datum point" +msgstr "点击放置基准点" + +#: graphicswin.cpp:1276 +msgid "click first point of line segment" +msgstr "点击线条的起点" + +#: graphicswin.cpp:1278 +msgid "click first point of construction line segment" +msgstr "点击构造线的起点" + +#: graphicswin.cpp:1279 +msgid "click first point of cubic segment" +msgstr "点击立方体的起点" + +#: graphicswin.cpp:1280 +msgid "click center of circle" +msgstr "点击圆弧的中心" + +#: graphicswin.cpp:1281 +msgid "click origin of workplane" +msgstr "点击工作面的原点" + +#: graphicswin.cpp:1282 +msgid "click one corner of rectangle" +msgstr "点击一个矩形倒角" + +#: graphicswin.cpp:1283 +msgid "click top left of text" +msgstr "点击文字左上角" + +#: graphicswin.cpp:1289 +msgid "click top left of image" +msgstr "点击图片左上角" + +#: graphicswin.cpp:1301 +msgid "" +"No entities are selected. Select entities before trying to toggle their " +"construction state." +msgstr "为选中实体,切换构造状态前请先选中实体对象。" + +#: group.cpp:86 +msgctxt "group-name" +msgid "sketch-in-3d" +msgstr "3D草图" + +#: group.cpp:142 +msgid "" +"Bad selection for new sketch in workplane. This group can be created with:\n" +"\n" +" * a point (through the point, orthogonal to coordinate axes)\n" +" * a point and two line segments (through the point, parallel to the " +"lines)\n" +" * a workplane (copy of the workplane)\n" +msgstr "" +"在新工作面内绘图选择失败,该组可以使用:\n" +"\n" +" * 一个点(通过该点,正交至坐标轴)\n" +" * 一个点和二个线段(通过点,绘制平行线至线段)\n" +" * 一个工作面(复制工作面)\n" + +#: group.cpp:154 +msgid "" +"Activate a workplane (Sketch -> In Workplane) before extruding. The sketch " +"will be extruded normal to the workplane." +msgstr "挤出前先激活工作面(草图->在工作面),该草图将由工作面的法线方向挤出。" + +#: group.cpp:163 +msgctxt "group-name" +msgid "extrude" +msgstr "挤出" + +#: group.cpp:168 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "扫略操作仅可用于二维草图。" + +#: group.cpp:179 +msgid "" +"Bad selection for new lathe group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" +"创建扫略组失败,该组可由:\n" +"\n" +" * 一个点和一个线段或法线(围绕坐标轴至线或法线的平行线,通过点)\n" +" * 一个线段(围绕线段)\n" + +#: group.cpp:189 +msgctxt "group-name" +msgid "lathe" +msgstr "扫略" + +#: group.cpp:194 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:205 +msgid "" +"Bad selection for new revolve group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:217 +msgctxt "group-name" +msgid "revolve" +msgstr "旋转" + +#: group.cpp:222 +msgid "Helix operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:233 +msgid "" +"Bad selection for new helix group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel " +"to line / normal, through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:245 +msgctxt "group-name" +msgid "helix" +msgstr "螺旋" + +#: group.cpp:258 +msgid "" +"Bad selection for new rotation. This group can be created with:\n" +"\n" +" * a point, while locked in workplane (rotate in plane, about that " +"point)\n" +" * a point and a line or a normal (rotate about an axis through the " +"point, and parallel to line / normal)\n" +msgstr "" + +#: group.cpp:271 +msgctxt "group-name" +msgid "rotate" +msgstr "旋转" + +#: group.cpp:282 +msgctxt "group-name" +msgid "translate" +msgstr "移动" + +#: group.cpp:400 +msgid "(unnamed)" +msgstr "(未命名)" + +#: groupmesh.cpp:708 +msgid "not closed contour, or not all same style!" +msgstr "未闭合轮廓,或样式不一致!" + +#: groupmesh.cpp:721 +msgid "points not all coplanar!" +msgstr "点不在相同平面!" + +#: groupmesh.cpp:723 +msgid "contour is self-intersecting!" +msgstr "轮廓自相交!" + +#: groupmesh.cpp:725 +msgid "zero-length edge!" +msgstr "边缘长度为零!" + +#: modify.cpp:254 +msgid "Must be sketching in workplane to create tangent arc." +msgstr "" + +#: modify.cpp:301 +msgid "" +"To create a tangent arc, select a point where two non-construction lines or " +"circles in this group and workplane join." +msgstr "" + +#: modify.cpp:388 +msgid "" +"Couldn't round this corner. Try a smaller radius, or try creating the " +"desired geometry by hand with tangency constraints." +msgstr "" + +#: modify.cpp:597 +msgid "Couldn't split this entity; lines, circles, or cubics only." +msgstr "" + +#: modify.cpp:624 +msgid "Must be sketching in workplane to split." +msgstr "" + +#: modify.cpp:631 +msgid "" +"Select two entities that intersect each other (e.g. two lines/circles/arcs " +"or a line/circle/arc and a point)." +msgstr "" + +#: modify.cpp:736 +msgid "Can't split; no intersection found." +msgstr "无法拆分;未发现较差点。" + +#: mouse.cpp:560 +msgid "Assign to Style" +msgstr "指定样式" + +#: mouse.cpp:576 +msgid "No Style" +msgstr "无样式" + +#: mouse.cpp:579 +msgid "Newly Created Custom Style..." +msgstr "新组样式。" + +#: mouse.cpp:586 +msgid "Group Info" +msgstr "组信息" + +#: mouse.cpp:606 +msgid "Style Info" +msgstr "样式信息" + +#: mouse.cpp:626 +msgid "Select Edge Chain" +msgstr "选择边缘链" + +#: mouse.cpp:632 +msgid "Toggle Reference Dimension" +msgstr "切换参考标注" + +#: mouse.cpp:638 +msgid "Other Supplementary Angle" +msgstr "其它补充角度" + +#: mouse.cpp:643 +msgid "Snap to Grid" +msgstr "捕捉至轴网" + +#: mouse.cpp:652 +msgid "Remove Spline Point" +msgstr "删除样条线的点" + +#: mouse.cpp:687 +msgid "Add Spline Point" +msgstr "增加样条线的点" + +#: mouse.cpp:691 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "无法增加样条线点:超过最大限制。" + +#: mouse.cpp:716 +msgid "Toggle Construction" +msgstr "切换构造" + +#: mouse.cpp:731 +msgid "Delete Point-Coincident Constraint" +msgstr "删除点一致约束" + +#: mouse.cpp:750 +msgid "Cut" +msgstr "剪切" + +#: mouse.cpp:752 +msgid "Copy" +msgstr "复制" + +#: mouse.cpp:756 +msgid "Select All" +msgstr "全选" + +#: mouse.cpp:761 +msgid "Paste" +msgstr "粘贴" + +#: mouse.cpp:763 +msgid "Paste Transformed..." +msgstr "粘贴移动的..." + +#: mouse.cpp:768 +msgid "Delete" +msgstr "删除" + +#: mouse.cpp:771 +msgid "Unselect All" +msgstr "取消全选" + +#: mouse.cpp:778 +msgid "Unselect Hovered" +msgstr "取消覆盖区域的全选" + +#: mouse.cpp:787 +msgid "Zoom to Fit" +msgstr "自动缩放" + +#: mouse.cpp:990 mouse.cpp:1277 +msgid "click next point of line, or press Esc" +msgstr "点击下一个点或取消(ESC)" + +#: mouse.cpp:996 +msgid "" +"Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "无法在3D内绘制矩形; 首先,激活工作面,草图->在工作面。" + +#: mouse.cpp:1030 +msgid "click to place other corner of rectangle" +msgstr "点击放置其它矩形倒角" + +#: mouse.cpp:1050 +msgid "click to set radius" +msgstr "点击设置半径" + +#: mouse.cpp:1055 +msgid "" +"Can't draw arc in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "无法在3D空间内绘制弧线,可使用 草图->在工作面 激活工作面。" + +#: mouse.cpp:1074 +msgid "click to place point" +msgstr "点击放置点" + +#: mouse.cpp:1090 +msgid "click next point of cubic, or press Esc" +msgstr "点击下一个点或取消(ESC)" + +#: mouse.cpp:1095 +msgid "" +"Sketching in a workplane already; sketch in 3d before creating new workplane." +msgstr "已经在工作面绘制;在新建工作面前在三维空间绘制。" + +#: mouse.cpp:1111 +msgid "" +"Can't draw text in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "无法在三维空间内绘制文字,可使用 草图->在工作面 激活工作面。" + +#: mouse.cpp:1128 +msgid "click to place bottom right of text" +msgstr "点击文字的右下角放置" + +#: mouse.cpp:1134 +msgid "" +"Can't draw image in 3d; first, activate a workplane with Sketch -> In " +"Workplane." +msgstr "无法在三维空间内绘制图片,可使用 草图->在工作面 激活工作面。" + +#: mouse.cpp:1161 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "新备注 - 双击编辑" + +#: platform/gui.cpp:85 platform/gui.cpp:89 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "SolveSpace模型" + +#: platform/gui.cpp:90 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "" + +#: platform/gui.cpp:94 +msgctxt "file-type" +msgid "PNG image" +msgstr "PNG图片" + +#: platform/gui.cpp:98 +msgctxt "file-type" +msgid "STL mesh" +msgstr "STL网格" + +#: platform/gui.cpp:99 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "Wavefront OBJ网格" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "Three.js-网格及查看视图" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "Three.js-仅网格" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Q3D Object file" +msgstr "Q3D对象文件" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "VRML text file" +msgstr "VRML文本文件" + +#: platform/gui.cpp:107 platform/gui.cpp:114 platform/gui.cpp:121 +msgctxt "file-type" +msgid "STEP file" +msgstr "STEP文件" + +#: platform/gui.cpp:111 +msgctxt "file-type" +msgid "PDF file" +msgstr "PDF文件" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "封装好的PostScript" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "SVG矢量图" + +#: platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "DXF文件(AutoCAD 2007)" + +#: platform/gui.cpp:116 +msgctxt "file-type" +msgid "HPGL file" +msgstr "HPGL文件" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "G Code" +msgstr "G Code" + +#: platform/gui.cpp:126 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "AutoCAD DXF/DWG文件" + +#: platform/gui.cpp:130 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "逗号分隔数据" + +#: platform/guigtk.cpp:1317 platform/guimac.mm:1360 platform/guiwin.cpp:1608 +msgid "untitled" +msgstr "未命名" + +#: platform/guigtk.cpp:1328 platform/guigtk.cpp:1361 platform/guimac.mm:1318 +#: platform/guiwin.cpp:1555 +msgctxt "title" +msgid "Save File" +msgstr "保存文件" + +#: platform/guigtk.cpp:1329 platform/guigtk.cpp:1362 platform/guimac.mm:1301 +#: platform/guiwin.cpp:1557 +msgctxt "title" +msgid "Open File" +msgstr "打开文件" + +#: platform/guigtk.cpp:1332 platform/guigtk.cpp:1368 +msgctxt "button" +msgid "_Cancel" +msgstr "取消_C" + +#: platform/guigtk.cpp:1333 platform/guigtk.cpp:1366 +msgctxt "button" +msgid "_Save" +msgstr "保存_S" + +#: platform/guigtk.cpp:1334 platform/guigtk.cpp:1367 +msgctxt "button" +msgid "_Open" +msgstr "打开_O" + +#: style.cpp:166 +msgid "" +"Can't assign style to an entity that's derived from another entity; try " +"assigning a style to this entity's parent." +msgstr "无法将样式分配给派生自其他实体的实体;尝试将样式分配给此实体的父级。" + +#: style.cpp:665 +msgid "Style name cannot be empty" +msgstr "样式名称不能为空" + +#: textscreens.cpp:741 +msgid "Can't repeat fewer than 1 time." +msgstr "不能重复少于 1 次。" + +#: textscreens.cpp:745 +msgid "Can't repeat more than 999 times." +msgstr "重复不超过 999 次。" + +#: textscreens.cpp:770 +msgid "Group name cannot be empty" +msgstr "组名称不能为空" + +#: textscreens.cpp:813 +msgid "Opacity must be between zero and one." +msgstr "不透明度必须在零和 1 之间。" + +#: textscreens.cpp:848 +msgid "Radius cannot be zero or negative." +msgstr "半径偏移不能为负数。" + +#: toolbar.cpp:18 +msgid "Sketch line segment" +msgstr "草图线段" + +#: toolbar.cpp:20 +msgid "Sketch rectangle" +msgstr "草图矩形" + +#: toolbar.cpp:22 +msgid "Sketch circle" +msgstr "草图圆" + +#: toolbar.cpp:24 +msgid "Sketch arc of a circle" +msgstr "圆的草图弧线" + +#: toolbar.cpp:26 +msgid "Sketch curves from text in a TrueType font" +msgstr "从 TrueType 字体中的文本中绘制草图曲线" + +#: toolbar.cpp:28 +msgid "Sketch image from a file" +msgstr "从文件中绘制图像" + +#: toolbar.cpp:30 +msgid "Create tangent arc at selected point" +msgstr "在选定点创建切线弧" + +#: toolbar.cpp:32 +msgid "Sketch cubic Bezier spline" +msgstr "草图立方贝塞尔样条" + +#: toolbar.cpp:34 +msgid "Sketch datum point" +msgstr "草图基准点" + +#: toolbar.cpp:36 +msgid "Toggle construction" +msgstr "切换结构" + +#: toolbar.cpp:38 +msgid "Split lines / curves where they intersect" +msgstr "相交的分割线/曲线" + +#: toolbar.cpp:42 +msgid "Constrain distance / diameter / length" +msgstr "约束距离/直径/长度" + +#: toolbar.cpp:44 +msgid "Constrain angle" +msgstr "约束角度" + +#: toolbar.cpp:46 +msgid "Constrain to be horizontal" +msgstr "约束为水平" + +#: toolbar.cpp:48 +msgid "Constrain to be vertical" +msgstr "约束为垂直" + +#: toolbar.cpp:50 +msgid "Constrain to be parallel or tangent" +msgstr "约束为平行或切线" + +#: toolbar.cpp:52 +msgid "Constrain to be perpendicular" +msgstr "约束至垂直" + +#: toolbar.cpp:54 +msgid "Constrain point on line / curve / plane / point" +msgstr "约束点至线/曲线/平面/点" + +#: toolbar.cpp:56 +msgid "Constrain symmetric" +msgstr "对称约束" + +#: toolbar.cpp:58 +msgid "Constrain equal length / radius / angle" +msgstr "约束长/半径/角度相等" + +#: toolbar.cpp:60 +msgid "Constrain normals in same orientation" +msgstr "约束法线在同原点" + +#: toolbar.cpp:62 +msgid "Other supplementary angle" +msgstr "其它补充角度" + +#: toolbar.cpp:64 +msgid "Toggle reference dimension" +msgstr "切换参考标注" + +#: toolbar.cpp:68 +msgid "New group extruding active sketch" +msgstr "新组中挤出当前草图" + +#: toolbar.cpp:70 +msgid "New group rotating active sketch" +msgstr "新组中旋转体当前草图" + +#: toolbar.cpp:72 +msgid "New group step and repeat rotating" +msgstr "新组中逐步重复旋转体" + +#: toolbar.cpp:74 +msgid "New group step and repeat translating" +msgstr "新组中逐步重复移动体" + +#: toolbar.cpp:76 +msgid "New group in new workplane (thru given entities)" +msgstr "在新工作平面创建组(通过指定对象)" + +#: toolbar.cpp:78 +msgid "New group in 3d" +msgstr "在3D中新建组" + +#: toolbar.cpp:80 +msgid "New group linking / assembling file" +msgstr "新组 连接/装配文件" + +#: toolbar.cpp:84 +msgid "Nearest isometric view" +msgstr "ISO视图" + +#: toolbar.cpp:86 +msgid "Align view to active workplane" +msgstr "切换视图至当前工作面" + +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "错误" + +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "消息" + +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "&OK" + +#: view.cpp:78 +msgid "Scale cannot be zero or negative." +msgstr "缩放不能为零。" + +#: view.cpp:90 view.cpp:99 +msgid "Bad format: specify x, y, z" +msgstr "格式错误: 需指定 x, y, z" diff --git a/res/messages.pot b/res/messages.pot new file mode 100644 index 0000000..7f02500 --- /dev/null +++ b/res/messages.pot @@ -0,0 +1,1653 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR the PACKAGE authors +# This file is distributed under the same license as the SolveSpace package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: SolveSpace 3.0\n" +"Report-Msgid-Bugs-To: whitequark@whitequark.org\n" +"POT-Creation-Date: 2020-11-17 20:50-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: clipboard.cpp:274 +msgid "" +"Cut, paste, and copy work only in a workplane.\n" +"\n" +"Activate one with Sketch -> In Workplane." +msgstr "" + +#: clipboard.cpp:291 +msgid "Clipboard is empty; nothing to paste." +msgstr "" + +#: clipboard.cpp:338 +msgid "Number of copies to paste must be at least one." +msgstr "" + +#: clipboard.cpp:354 textscreens.cpp:783 +msgid "Scale cannot be zero." +msgstr "" + +#: clipboard.cpp:396 +msgid "Select one point to define origin of rotation." +msgstr "" + +#: clipboard.cpp:408 +msgid "Select two points to define translation vector." +msgstr "" + +#: clipboard.cpp:418 +msgid "Transformation is identity. So all copies will be exactly on top of each other." +msgstr "" + +#: clipboard.cpp:422 +msgid "Too many items to paste; split this into smaller pastes." +msgstr "" + +#: clipboard.cpp:427 +msgid "No workplane active." +msgstr "" + +#: confscreen.cpp:410 +msgid "Bad format: specify coordinates as x, y, z" +msgstr "" + +#: confscreen.cpp:420 style.cpp:659 textscreens.cpp:805 +msgid "Bad format: specify color as r, g, b" +msgstr "" + +#: confscreen.cpp:446 +msgid "" +"The perspective factor will have no effect until you enable View -> Use Perspective Projection." +msgstr "" + +#: confscreen.cpp:459 confscreen.cpp:469 +#, c-format +msgid "Specify between 0 and %d digits after the decimal." +msgstr "" + +#: confscreen.cpp:481 +msgid "Export scale must not be zero!" +msgstr "" + +#: confscreen.cpp:493 +msgid "Cutter radius offset must not be negative!" +msgstr "" + +#: confscreen.cpp:547 +msgid "Bad value: autosave interval should be positive" +msgstr "" + +#: confscreen.cpp:550 +msgid "Bad format: specify interval in integral minutes" +msgstr "" + +#: constraint.cpp:12 +msgctxt "constr-name" +msgid "pts-coincident" +msgstr "" + +#: constraint.cpp:13 +msgctxt "constr-name" +msgid "pt-pt-distance" +msgstr "" + +#: constraint.cpp:14 +msgctxt "constr-name" +msgid "pt-line-distance" +msgstr "" + +#: constraint.cpp:15 +msgctxt "constr-name" +msgid "pt-plane-distance" +msgstr "" + +#: constraint.cpp:16 +msgctxt "constr-name" +msgid "pt-face-distance" +msgstr "" + +#: constraint.cpp:17 +msgctxt "constr-name" +msgid "proj-pt-pt-distance" +msgstr "" + +#: constraint.cpp:18 +msgctxt "constr-name" +msgid "pt-in-plane" +msgstr "" + +#: constraint.cpp:19 +msgctxt "constr-name" +msgid "pt-on-line" +msgstr "" + +#: constraint.cpp:20 +msgctxt "constr-name" +msgid "pt-on-face" +msgstr "" + +#: constraint.cpp:21 +msgctxt "constr-name" +msgid "eq-length" +msgstr "" + +#: constraint.cpp:22 +msgctxt "constr-name" +msgid "eq-length-and-pt-ln-dist" +msgstr "" + +#: constraint.cpp:23 +msgctxt "constr-name" +msgid "eq-pt-line-distances" +msgstr "" + +#: constraint.cpp:24 +msgctxt "constr-name" +msgid "length-ratio" +msgstr "" + +#: constraint.cpp:25 +msgctxt "constr-name" +msgid "length-difference" +msgstr "" + +#: constraint.cpp:26 +msgctxt "constr-name" +msgid "symmetric" +msgstr "" + +#: constraint.cpp:27 +msgctxt "constr-name" +msgid "symmetric-h" +msgstr "" + +#: constraint.cpp:28 +msgctxt "constr-name" +msgid "symmetric-v" +msgstr "" + +#: constraint.cpp:29 +msgctxt "constr-name" +msgid "symmetric-line" +msgstr "" + +#: constraint.cpp:30 +msgctxt "constr-name" +msgid "at-midpoint" +msgstr "" + +#: constraint.cpp:31 +msgctxt "constr-name" +msgid "horizontal" +msgstr "" + +#: constraint.cpp:32 +msgctxt "constr-name" +msgid "vertical" +msgstr "" + +#: constraint.cpp:33 +msgctxt "constr-name" +msgid "diameter" +msgstr "" + +#: constraint.cpp:34 +msgctxt "constr-name" +msgid "pt-on-circle" +msgstr "" + +#: constraint.cpp:35 +msgctxt "constr-name" +msgid "same-orientation" +msgstr "" + +#: constraint.cpp:36 +msgctxt "constr-name" +msgid "angle" +msgstr "" + +#: constraint.cpp:37 +msgctxt "constr-name" +msgid "parallel" +msgstr "" + +#: constraint.cpp:38 +msgctxt "constr-name" +msgid "arc-line-tangent" +msgstr "" + +#: constraint.cpp:39 +msgctxt "constr-name" +msgid "cubic-line-tangent" +msgstr "" + +#: constraint.cpp:40 +msgctxt "constr-name" +msgid "curve-curve-tangent" +msgstr "" + +#: constraint.cpp:41 +msgctxt "constr-name" +msgid "perpendicular" +msgstr "" + +#: constraint.cpp:42 +msgctxt "constr-name" +msgid "eq-radius" +msgstr "" + +#: constraint.cpp:43 +msgctxt "constr-name" +msgid "eq-angle" +msgstr "" + +#: constraint.cpp:44 +msgctxt "constr-name" +msgid "eq-line-len-arc-len" +msgstr "" + +#: constraint.cpp:45 +msgctxt "constr-name" +msgid "lock-where-dragged" +msgstr "" + +#: constraint.cpp:46 +msgctxt "constr-name" +msgid "comment" +msgstr "" + +#: constraint.cpp:171 +msgid "" +"Bad selection for distance / diameter constraint. This constraint can apply to:\n" +"\n" +" * two points (distance between points)\n" +" * a line segment (length)\n" +" * two points and a line segment or normal (projected distance)\n" +" * a workplane and a point (minimum distance)\n" +" * a line segment and a point (minimum distance)\n" +" * a plane face and a point (minimum distance)\n" +" * a circle or an arc (diameter)\n" +msgstr "" + +#: constraint.cpp:224 +msgid "" +"Bad selection for on point / curve / plane constraint. This constraint can apply to:\n" +"\n" +" * two points (points coincident)\n" +" * a point and a workplane (point in plane)\n" +" * a point and a line segment (point on line)\n" +" * a point and a circle or arc (point on curve)\n" +" * a point and a plane face (point on face)\n" +msgstr "" + +#: constraint.cpp:286 +msgid "" +"Bad selection for equal length / radius constraint. This constraint can apply to:\n" +"\n" +" * two line segments (equal length)\n" +" * two line segments and two points (equal point-line distances)\n" +" * a line segment and two points (equal point-line distances)\n" +" * a line segment, and a point and line segment (point-line distance equals length)\n" +" * four line segments or normals (equal angle between A,B and C,D)\n" +" * three line segments or normals (equal angle between A,B and B,C)\n" +" * two circles or arcs (equal radius)\n" +" * a line segment and an arc (line segment length equals arc length)\n" +msgstr "" + +#: constraint.cpp:325 +msgid "" +"Bad selection for length ratio constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +msgstr "" + +#: constraint.cpp:342 +msgid "" +"Bad selection for length difference constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +msgstr "" + +#: constraint.cpp:368 +msgid "" +"Bad selection for at midpoint constraint. This constraint can apply to:\n" +"\n" +" * a line segment and a point (point at midpoint)\n" +" * a line segment and a workplane (line's midpoint on plane)\n" +msgstr "" + +#: constraint.cpp:426 +msgid "" +"Bad selection for symmetric constraint. This constraint can apply to:\n" +"\n" +" * two points or a line segment (symmetric about workplane's coordinate axis)\n" +" * line segment, and two points or a line segment (symmetric about line segment)\n" +" * workplane, and two points or a line segment (symmetric about workplane)\n" +msgstr "" + +#: constraint.cpp:440 +msgid "A workplane must be active when constraining symmetric without an explicit symmetry plane." +msgstr "" + +#: constraint.cpp:470 +msgid "" +"Activate a workplane (with Sketch -> In Workplane) before applying a horizontal or vertical " +"constraint." +msgstr "" + +#: constraint.cpp:483 +msgid "" +"Bad selection for horizontal / vertical constraint. This constraint can apply to:\n" +"\n" +" * two points\n" +" * a line segment\n" +msgstr "" + +#: constraint.cpp:504 +msgid "" +"Bad selection for same orientation constraint. This constraint can apply to:\n" +"\n" +" * two normals\n" +msgstr "" + +#: constraint.cpp:554 +msgid "Must select an angle constraint." +msgstr "" + +#: constraint.cpp:567 +msgid "Must select a constraint with associated label." +msgstr "" + +#: constraint.cpp:578 +msgid "" +"Bad selection for angle constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" + +#: constraint.cpp:635 +msgid "" +"The tangent arc and line segment must share an endpoint. Constrain them with Constrain -> On " +"Point before constraining tangent." +msgstr "" + +#: constraint.cpp:659 +msgid "" +"The tangent cubic and line segment must share an endpoint. Constrain them with Constrain -> On " +"Point before constraining tangent." +msgstr "" + +#: constraint.cpp:669 +msgid "Curve-curve tangency must apply in workplane." +msgstr "" + +#: constraint.cpp:687 +msgid "" +"The curves must share an endpoint. Constrain them with Constrain -> On Point before constraining " +"tangent." +msgstr "" + +#: constraint.cpp:696 +msgid "" +"Bad selection for parallel / tangent constraint. This constraint can apply to:\n" +"\n" +" * two line segments (parallel)\n" +" * a line segment and a normal (parallel)\n" +" * two normals (parallel)\n" +" * two line segments, arcs, or beziers, that share an endpoint (tangent)\n" +msgstr "" + +#: constraint.cpp:714 +msgid "" +"Bad selection for perpendicular constraint. This constraint can apply to:\n" +"\n" +" * two line segments\n" +" * a line segment and a normal\n" +" * two normals\n" +msgstr "" + +#: constraint.cpp:729 +msgid "" +"Bad selection for lock point where dragged constraint. This constraint can apply to:\n" +"\n" +" * a point\n" +msgstr "" + +#: constraint.cpp:740 +msgid "click center of comment text" +msgstr "" + +#: export.cpp:19 +msgid "" +"No solid model present; draw one with extrudes and revolves, or use Export 2d View to export bare " +"lines and curves." +msgstr "" + +#: export.cpp:61 +msgid "" +"Bad selection for export section. Please select:\n" +"\n" +" * nothing, with an active workplane (workplane is section plane)\n" +" * a face (section plane through face)\n" +" * a point and two line segments (plane through point and parallel to lines)\n" +msgstr "" + +#: export.cpp:822 +msgid "Active group mesh is empty; nothing to export." +msgstr "" + +#: exportvector.cpp:337 +msgid "freehand lines were replaced with continuous lines" +msgstr "" + +#: exportvector.cpp:339 +msgid "zigzag lines were replaced with continuous lines" +msgstr "" + +#: exportvector.cpp:593 +msgid "Some aspects of the drawing have no DXF equivalent and were not exported:\n" +msgstr "" + +#: exportvector.cpp:839 +msgid "PDF page size exceeds 200 by 200 inches; many viewers may reject this file." +msgstr "" + +#: file.cpp:44 group.cpp:91 +msgctxt "group-name" +msgid "sketch-in-plane" +msgstr "" + +#: file.cpp:62 +msgctxt "group-name" +msgid "#references" +msgstr "" + +#: file.cpp:549 +msgid "Unrecognized data in file. This file may be corrupt, or from a newer version of the program." +msgstr "" + +#: file.cpp:859 +msgctxt "title" +msgid "Missing File" +msgstr "" + +#: file.cpp:860 +#, c-format +msgctxt "dialog" +msgid "The linked file “%s” is not present." +msgstr "" + +#: file.cpp:862 +msgctxt "dialog" +msgid "" +"Do you want to locate it manually?\n" +"\n" +"If you decline, any geometry that depends on the missing file will be permanently removed." +msgstr "" + +#: file.cpp:865 +msgctxt "button" +msgid "&Yes" +msgstr "" + +#: file.cpp:867 +msgctxt "button" +msgid "&No" +msgstr "" + +#: file.cpp:869 +msgctxt "button" +msgid "&Cancel" +msgstr "" + +#: graphicswin.cpp:41 +msgid "&File" +msgstr "" + +#: graphicswin.cpp:42 +msgid "&New" +msgstr "" + +#: graphicswin.cpp:43 +msgid "&Open..." +msgstr "" + +#: graphicswin.cpp:44 +msgid "Open &Recent" +msgstr "" + +#: graphicswin.cpp:45 +msgid "&Save" +msgstr "" + +#: graphicswin.cpp:46 +msgid "Save &As..." +msgstr "" + +#: graphicswin.cpp:48 +msgid "Export &Image..." +msgstr "" + +#: graphicswin.cpp:49 +msgid "Export 2d &View..." +msgstr "" + +#: graphicswin.cpp:50 +msgid "Export 2d &Section..." +msgstr "" + +#: graphicswin.cpp:51 +msgid "Export 3d &Wireframe..." +msgstr "" + +#: graphicswin.cpp:52 +msgid "Export Triangle &Mesh..." +msgstr "" + +#: graphicswin.cpp:53 +msgid "Export &Surfaces..." +msgstr "" + +#: graphicswin.cpp:54 +msgid "Im&port..." +msgstr "" + +#: graphicswin.cpp:57 +msgid "E&xit" +msgstr "" + +#: graphicswin.cpp:60 +msgid "&Edit" +msgstr "" + +#: graphicswin.cpp:61 +msgid "&Undo" +msgstr "" + +#: graphicswin.cpp:62 +msgid "&Redo" +msgstr "" + +#: graphicswin.cpp:63 +msgid "Re&generate All" +msgstr "" + +#: graphicswin.cpp:65 +msgid "Snap Selection to &Grid" +msgstr "" + +#: graphicswin.cpp:66 +msgid "Rotate Imported &90°" +msgstr "" + +#: graphicswin.cpp:68 +msgid "Cu&t" +msgstr "" + +#: graphicswin.cpp:69 +msgid "&Copy" +msgstr "" + +#: graphicswin.cpp:70 +msgid "&Paste" +msgstr "" + +#: graphicswin.cpp:71 +msgid "Paste &Transformed..." +msgstr "" + +#: graphicswin.cpp:72 +msgid "&Delete" +msgstr "" + +#: graphicswin.cpp:74 +msgid "Select &Edge Chain" +msgstr "" + +#: graphicswin.cpp:75 +msgid "Select &All" +msgstr "" + +#: graphicswin.cpp:76 +msgid "&Unselect All" +msgstr "" + +#: graphicswin.cpp:78 +msgid "&Line Styles..." +msgstr "" + +#: graphicswin.cpp:79 +msgid "&View Projection..." +msgstr "" + +#: graphicswin.cpp:81 +msgid "Con&figuration..." +msgstr "" + +#: graphicswin.cpp:84 +msgid "&View" +msgstr "" + +#: graphicswin.cpp:85 +msgid "Zoom &In" +msgstr "" + +#: graphicswin.cpp:86 +msgid "Zoom &Out" +msgstr "" + +#: graphicswin.cpp:87 +msgid "Zoom To &Fit" +msgstr "" + +#: graphicswin.cpp:89 +msgid "Align View to &Workplane" +msgstr "" + +#: graphicswin.cpp:90 +msgid "Nearest &Ortho View" +msgstr "" + +#: graphicswin.cpp:91 +msgid "Nearest &Isometric View" +msgstr "" + +#: graphicswin.cpp:92 +msgid "&Center View At Point" +msgstr "" + +#: graphicswin.cpp:94 +msgid "Show Snap &Grid" +msgstr "" + +#: graphicswin.cpp:95 +msgid "Use &Perspective Projection" +msgstr "" + +#: graphicswin.cpp:96 +msgid "Dimension &Units" +msgstr "" + +#: graphicswin.cpp:97 +msgid "Dimensions in &Millimeters" +msgstr "" + +#: graphicswin.cpp:98 +msgid "Dimensions in M&eters" +msgstr "" + +#: graphicswin.cpp:99 +msgid "Dimensions in &Inches" +msgstr "" + +#: graphicswin.cpp:101 +msgid "Show &Toolbar" +msgstr "" + +#: graphicswin.cpp:102 +msgid "Show Property Bro&wser" +msgstr "" + +#: graphicswin.cpp:104 +msgid "&Full Screen" +msgstr "" + +#: graphicswin.cpp:106 +msgid "&New Group" +msgstr "" + +#: graphicswin.cpp:107 +msgid "Sketch In &3d" +msgstr "" + +#: graphicswin.cpp:108 +msgid "Sketch In New &Workplane" +msgstr "" + +#: graphicswin.cpp:110 +msgid "Step &Translating" +msgstr "" + +#: graphicswin.cpp:111 +msgid "Step &Rotating" +msgstr "" + +#: graphicswin.cpp:113 +msgid "E&xtrude" +msgstr "" + +#: graphicswin.cpp:114 +msgid "&Helix" +msgstr "" + +#: graphicswin.cpp:115 +msgid "&Lathe" +msgstr "" + +#: graphicswin.cpp:116 +msgid "Re&volve" +msgstr "" + +#: graphicswin.cpp:118 +msgid "Link / Assemble..." +msgstr "" + +#: graphicswin.cpp:119 +msgid "Link Recent" +msgstr "" + +#: graphicswin.cpp:121 +msgid "&Sketch" +msgstr "" + +#: graphicswin.cpp:122 +msgid "In &Workplane" +msgstr "" + +#: graphicswin.cpp:123 +msgid "Anywhere In &3d" +msgstr "" + +#: graphicswin.cpp:125 +msgid "Datum &Point" +msgstr "" + +#: graphicswin.cpp:126 +msgid "&Workplane" +msgstr "" + +#: graphicswin.cpp:128 +msgid "Line &Segment" +msgstr "" + +#: graphicswin.cpp:129 +msgid "C&onstruction Line Segment" +msgstr "" + +#: graphicswin.cpp:130 +msgid "&Rectangle" +msgstr "" + +#: graphicswin.cpp:131 +msgid "&Circle" +msgstr "" + +#: graphicswin.cpp:132 +msgid "&Arc of a Circle" +msgstr "" + +#: graphicswin.cpp:133 +msgid "&Bezier Cubic Spline" +msgstr "" + +#: graphicswin.cpp:135 +msgid "&Text in TrueType Font" +msgstr "" + +#: graphicswin.cpp:136 +msgid "&Image" +msgstr "" + +#: graphicswin.cpp:138 +msgid "To&ggle Construction" +msgstr "" + +#: graphicswin.cpp:139 +msgid "Tangent &Arc at Point" +msgstr "" + +#: graphicswin.cpp:140 +msgid "Split Curves at &Intersection" +msgstr "" + +#: graphicswin.cpp:142 +msgid "&Constrain" +msgstr "" + +#: graphicswin.cpp:143 +msgid "&Distance / Diameter" +msgstr "" + +#: graphicswin.cpp:144 +msgid "Re&ference Dimension" +msgstr "" + +#: graphicswin.cpp:145 +msgid "A&ngle" +msgstr "" + +#: graphicswin.cpp:146 +msgid "Reference An&gle" +msgstr "" + +#: graphicswin.cpp:147 +msgid "Other S&upplementary Angle" +msgstr "" + +#: graphicswin.cpp:148 +msgid "Toggle R&eference Dim" +msgstr "" + +#: graphicswin.cpp:150 +msgid "&Horizontal" +msgstr "" + +#: graphicswin.cpp:151 +msgid "&Vertical" +msgstr "" + +#: graphicswin.cpp:153 +msgid "&On Point / Curve / Plane" +msgstr "" + +#: graphicswin.cpp:154 +msgid "E&qual Length / Radius / Angle" +msgstr "" + +#: graphicswin.cpp:155 +msgid "Length Ra&tio" +msgstr "" + +#: graphicswin.cpp:156 +msgid "Length Diff&erence" +msgstr "" + +#: graphicswin.cpp:157 +msgid "At &Midpoint" +msgstr "" + +#: graphicswin.cpp:158 +msgid "S&ymmetric" +msgstr "" + +#: graphicswin.cpp:159 +msgid "Para&llel / Tangent" +msgstr "" + +#: graphicswin.cpp:160 +msgid "&Perpendicular" +msgstr "" + +#: graphicswin.cpp:161 +msgid "Same Orient&ation" +msgstr "" + +#: graphicswin.cpp:162 +msgid "Lock Point Where &Dragged" +msgstr "" + +#: graphicswin.cpp:164 +msgid "Comment" +msgstr "" + +#: graphicswin.cpp:166 +msgid "&Analyze" +msgstr "" + +#: graphicswin.cpp:167 +msgid "Measure &Volume" +msgstr "" + +#: graphicswin.cpp:168 +msgid "Measure A&rea" +msgstr "" + +#: graphicswin.cpp:169 +msgid "Measure &Perimeter" +msgstr "" + +#: graphicswin.cpp:170 +msgid "Show &Interfering Parts" +msgstr "" + +#: graphicswin.cpp:171 +msgid "Show &Naked Edges" +msgstr "" + +#: graphicswin.cpp:172 +msgid "Show &Center of Mass" +msgstr "" + +#: graphicswin.cpp:174 +msgid "Show &Underconstrained Points" +msgstr "" + +#: graphicswin.cpp:176 +msgid "&Trace Point" +msgstr "" + +#: graphicswin.cpp:177 +msgid "&Stop Tracing..." +msgstr "" + +#: graphicswin.cpp:178 +msgid "Step &Dimension..." +msgstr "" + +#: graphicswin.cpp:180 +msgid "&Help" +msgstr "" + +#: graphicswin.cpp:181 +msgid "&Language" +msgstr "" + +#: graphicswin.cpp:182 +msgid "&Website / Manual" +msgstr "" + +#: graphicswin.cpp:184 +msgid "&About" +msgstr "" + +#: graphicswin.cpp:352 +msgid "(no recent files)" +msgstr "" + +#: graphicswin.cpp:360 +#, c-format +msgid "File '%s' does not exist." +msgstr "" + +#: graphicswin.cpp:721 +msgid "No workplane is active, so the grid will not appear." +msgstr "" + +#: graphicswin.cpp:730 +msgid "" +"The perspective factor is set to zero, so the view will always be a parallel projection.\n" +"\n" +"For a perspective projection, modify the perspective factor in the configuration screen. A value " +"around 0.3 is typical." +msgstr "" + +#: graphicswin.cpp:809 +msgid "Select a point; this point will become the center of the view on screen." +msgstr "" + +#: graphicswin.cpp:1103 +msgid "No additional entities share endpoints with the selected entities." +msgstr "" + +#: graphicswin.cpp:1121 +msgid "" +"To use this command, select a point or other entity from an linked part, or make a link group the " +"active group." +msgstr "" + +#: graphicswin.cpp:1144 +msgid "" +"No workplane is active. Activate a workplane (with Sketch -> In Workplane) to define the plane " +"for the snap grid." +msgstr "" + +#: graphicswin.cpp:1151 +msgid "" +"Can't snap these items to grid; select points, text comments, or constraints with a label. To " +"snap a line, select its endpoints." +msgstr "" + +#: graphicswin.cpp:1239 +msgid "No workplane selected. Activating default workplane for this group." +msgstr "" + +#: graphicswin.cpp:1242 +msgid "" +"No workplane is selected, and the active group does not have a default workplane. Try selecting a " +"workplane, or activating a sketch-in-new-workplane group." +msgstr "" + +#: graphicswin.cpp:1263 +msgid "" +"Bad selection for tangent arc at point. Select a single point, or select nothing to set up arc " +"parameters." +msgstr "" + +#: graphicswin.cpp:1274 +msgid "click point on arc (draws anti-clockwise)" +msgstr "" + +#: graphicswin.cpp:1275 +msgid "click to place datum point" +msgstr "" + +#: graphicswin.cpp:1276 +msgid "click first point of line segment" +msgstr "" + +#: graphicswin.cpp:1278 +msgid "click first point of construction line segment" +msgstr "" + +#: graphicswin.cpp:1279 +msgid "click first point of cubic segment" +msgstr "" + +#: graphicswin.cpp:1280 +msgid "click center of circle" +msgstr "" + +#: graphicswin.cpp:1281 +msgid "click origin of workplane" +msgstr "" + +#: graphicswin.cpp:1282 +msgid "click one corner of rectangle" +msgstr "" + +#: graphicswin.cpp:1283 +msgid "click top left of text" +msgstr "" + +#: graphicswin.cpp:1289 +msgid "click top left of image" +msgstr "" + +#: graphicswin.cpp:1301 +msgid "No entities are selected. Select entities before trying to toggle their construction state." +msgstr "" + +#: group.cpp:86 +msgctxt "group-name" +msgid "sketch-in-3d" +msgstr "" + +#: group.cpp:142 +msgid "" +"Bad selection for new sketch in workplane. This group can be created with:\n" +"\n" +" * a point (through the point, orthogonal to coordinate axes)\n" +" * a point and two line segments (through the point, parallel to the lines)\n" +" * a workplane (copy of the workplane)\n" +msgstr "" + +#: group.cpp:154 +msgid "" +"Activate a workplane (Sketch -> In Workplane) before extruding. The sketch will be extruded " +"normal to the workplane." +msgstr "" + +#: group.cpp:163 +msgctxt "group-name" +msgid "extrude" +msgstr "" + +#: group.cpp:168 +msgid "Lathe operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:179 +msgid "" +"Bad selection for new lathe group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel to line / normal, " +"through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:189 +msgctxt "group-name" +msgid "lathe" +msgstr "" + +#: group.cpp:194 +msgid "Revolve operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:205 +msgid "" +"Bad selection for new revolve group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel to line / normal, " +"through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:217 +msgctxt "group-name" +msgid "revolve" +msgstr "" + +#: group.cpp:222 +msgid "Helix operation can only be applied to planar sketches." +msgstr "" + +#: group.cpp:233 +msgid "" +"Bad selection for new helix group. This group can be created with:\n" +"\n" +" * a point and a line segment or normal (revolved about an axis parallel to line / normal, " +"through point)\n" +" * a line segment (revolved about line segment)\n" +msgstr "" + +#: group.cpp:245 +msgctxt "group-name" +msgid "helix" +msgstr "" + +#: group.cpp:258 +msgid "" +"Bad selection for new rotation. This group can be created with:\n" +"\n" +" * a point, while locked in workplane (rotate in plane, about that point)\n" +" * a point and a line or a normal (rotate about an axis through the point, and parallel to " +"line / normal)\n" +msgstr "" + +#: group.cpp:271 +msgctxt "group-name" +msgid "rotate" +msgstr "" + +#: group.cpp:282 +msgctxt "group-name" +msgid "translate" +msgstr "" + +#: group.cpp:400 +msgid "(unnamed)" +msgstr "" + +#: groupmesh.cpp:708 +msgid "not closed contour, or not all same style!" +msgstr "" + +#: groupmesh.cpp:721 +msgid "points not all coplanar!" +msgstr "" + +#: groupmesh.cpp:723 +msgid "contour is self-intersecting!" +msgstr "" + +#: groupmesh.cpp:725 +msgid "zero-length edge!" +msgstr "" + +#: modify.cpp:254 +msgid "Must be sketching in workplane to create tangent arc." +msgstr "" + +#: modify.cpp:301 +msgid "" +"To create a tangent arc, select a point where two non-construction lines or circles in this group " +"and workplane join." +msgstr "" + +#: modify.cpp:388 +msgid "" +"Couldn't round this corner. Try a smaller radius, or try creating the desired geometry by hand " +"with tangency constraints." +msgstr "" + +#: modify.cpp:597 +msgid "Couldn't split this entity; lines, circles, or cubics only." +msgstr "" + +#: modify.cpp:624 +msgid "Must be sketching in workplane to split." +msgstr "" + +#: modify.cpp:631 +msgid "" +"Select two entities that intersect each other (e.g. two lines/circles/arcs or a line/circle/arc " +"and a point)." +msgstr "" + +#: modify.cpp:736 +msgid "Can't split; no intersection found." +msgstr "" + +#: mouse.cpp:560 +msgid "Assign to Style" +msgstr "" + +#: mouse.cpp:576 +msgid "No Style" +msgstr "" + +#: mouse.cpp:579 +msgid "Newly Created Custom Style..." +msgstr "" + +#: mouse.cpp:586 +msgid "Group Info" +msgstr "" + +#: mouse.cpp:606 +msgid "Style Info" +msgstr "" + +#: mouse.cpp:626 +msgid "Select Edge Chain" +msgstr "" + +#: mouse.cpp:632 +msgid "Toggle Reference Dimension" +msgstr "" + +#: mouse.cpp:638 +msgid "Other Supplementary Angle" +msgstr "" + +#: mouse.cpp:643 +msgid "Snap to Grid" +msgstr "" + +#: mouse.cpp:652 +msgid "Remove Spline Point" +msgstr "" + +#: mouse.cpp:687 +msgid "Add Spline Point" +msgstr "" + +#: mouse.cpp:691 +msgid "Cannot add spline point: maximum number of points reached." +msgstr "" + +#: mouse.cpp:716 +msgid "Toggle Construction" +msgstr "" + +#: mouse.cpp:731 +msgid "Delete Point-Coincident Constraint" +msgstr "" + +#: mouse.cpp:750 +msgid "Cut" +msgstr "" + +#: mouse.cpp:752 +msgid "Copy" +msgstr "" + +#: mouse.cpp:756 +msgid "Select All" +msgstr "" + +#: mouse.cpp:761 +msgid "Paste" +msgstr "" + +#: mouse.cpp:763 +msgid "Paste Transformed..." +msgstr "" + +#: mouse.cpp:768 +msgid "Delete" +msgstr "" + +#: mouse.cpp:771 +msgid "Unselect All" +msgstr "" + +#: mouse.cpp:778 +msgid "Unselect Hovered" +msgstr "" + +#: mouse.cpp:787 +msgid "Zoom to Fit" +msgstr "" + +#: mouse.cpp:990 mouse.cpp:1277 +msgid "click next point of line, or press Esc" +msgstr "" + +#: mouse.cpp:996 +msgid "Can't draw rectangle in 3d; first, activate a workplane with Sketch -> In Workplane." +msgstr "" + +#: mouse.cpp:1030 +msgid "click to place other corner of rectangle" +msgstr "" + +#: mouse.cpp:1050 +msgid "click to set radius" +msgstr "" + +#: mouse.cpp:1055 +msgid "Can't draw arc in 3d; first, activate a workplane with Sketch -> In Workplane." +msgstr "" + +#: mouse.cpp:1074 +msgid "click to place point" +msgstr "" + +#: mouse.cpp:1090 +msgid "click next point of cubic, or press Esc" +msgstr "" + +#: mouse.cpp:1095 +msgid "Sketching in a workplane already; sketch in 3d before creating new workplane." +msgstr "" + +#: mouse.cpp:1111 +msgid "Can't draw text in 3d; first, activate a workplane with Sketch -> In Workplane." +msgstr "" + +#: mouse.cpp:1128 +msgid "click to place bottom right of text" +msgstr "" + +#: mouse.cpp:1134 +msgid "Can't draw image in 3d; first, activate a workplane with Sketch -> In Workplane." +msgstr "" + +#: mouse.cpp:1161 +msgid "NEW COMMENT -- DOUBLE-CLICK TO EDIT" +msgstr "" + +#: platform/gui.cpp:85 platform/gui.cpp:89 +msgctxt "file-type" +msgid "SolveSpace models" +msgstr "" + +#: platform/gui.cpp:90 +msgctxt "file-type" +msgid "IDF circuit board" +msgstr "" + +#: platform/gui.cpp:94 +msgctxt "file-type" +msgid "PNG image" +msgstr "" + +#: platform/gui.cpp:98 +msgctxt "file-type" +msgid "STL mesh" +msgstr "" + +#: platform/gui.cpp:99 +msgctxt "file-type" +msgid "Wavefront OBJ mesh" +msgstr "" + +#: platform/gui.cpp:100 +msgctxt "file-type" +msgid "Three.js-compatible mesh, with viewer" +msgstr "" + +#: platform/gui.cpp:101 +msgctxt "file-type" +msgid "Three.js-compatible mesh, mesh only" +msgstr "" + +#: platform/gui.cpp:102 +msgctxt "file-type" +msgid "Q3D Object file" +msgstr "" + +#: platform/gui.cpp:103 +msgctxt "file-type" +msgid "VRML text file" +msgstr "" + +#: platform/gui.cpp:107 platform/gui.cpp:114 platform/gui.cpp:121 +msgctxt "file-type" +msgid "STEP file" +msgstr "" + +#: platform/gui.cpp:111 +msgctxt "file-type" +msgid "PDF file" +msgstr "" + +#: platform/gui.cpp:112 +msgctxt "file-type" +msgid "Encapsulated PostScript" +msgstr "" + +#: platform/gui.cpp:113 +msgctxt "file-type" +msgid "Scalable Vector Graphics" +msgstr "" + +#: platform/gui.cpp:115 platform/gui.cpp:122 +msgctxt "file-type" +msgid "DXF file (AutoCAD 2007)" +msgstr "" + +#: platform/gui.cpp:116 +msgctxt "file-type" +msgid "HPGL file" +msgstr "" + +#: platform/gui.cpp:117 +msgctxt "file-type" +msgid "G Code" +msgstr "" + +#: platform/gui.cpp:126 +msgctxt "file-type" +msgid "AutoCAD DXF and DWG files" +msgstr "" + +#: platform/gui.cpp:130 +msgctxt "file-type" +msgid "Comma-separated values" +msgstr "" + +#: platform/guigtk.cpp:1317 platform/guimac.mm:1360 platform/guiwin.cpp:1608 +msgid "untitled" +msgstr "" + +#: platform/guigtk.cpp:1328 platform/guigtk.cpp:1361 platform/guimac.mm:1318 +#: platform/guiwin.cpp:1555 +msgctxt "title" +msgid "Save File" +msgstr "" + +#: platform/guigtk.cpp:1329 platform/guigtk.cpp:1362 platform/guimac.mm:1301 +#: platform/guiwin.cpp:1557 +msgctxt "title" +msgid "Open File" +msgstr "" + +#: platform/guigtk.cpp:1332 platform/guigtk.cpp:1368 +msgctxt "button" +msgid "_Cancel" +msgstr "" + +#: platform/guigtk.cpp:1333 platform/guigtk.cpp:1366 +msgctxt "button" +msgid "_Save" +msgstr "" + +#: platform/guigtk.cpp:1334 platform/guigtk.cpp:1367 +msgctxt "button" +msgid "_Open" +msgstr "" + +#: style.cpp:166 +msgid "" +"Can't assign style to an entity that's derived from another entity; try assigning a style to this " +"entity's parent." +msgstr "" + +#: style.cpp:665 +msgid "Style name cannot be empty" +msgstr "" + +#: textscreens.cpp:741 +msgid "Can't repeat fewer than 1 time." +msgstr "" + +#: textscreens.cpp:745 +msgid "Can't repeat more than 999 times." +msgstr "" + +#: textscreens.cpp:770 +msgid "Group name cannot be empty" +msgstr "" + +#: textscreens.cpp:813 +msgid "Opacity must be between zero and one." +msgstr "" + +#: textscreens.cpp:848 +msgid "Radius cannot be zero or negative." +msgstr "" + +#: toolbar.cpp:18 +msgid "Sketch line segment" +msgstr "" + +#: toolbar.cpp:20 +msgid "Sketch rectangle" +msgstr "" + +#: toolbar.cpp:22 +msgid "Sketch circle" +msgstr "" + +#: toolbar.cpp:24 +msgid "Sketch arc of a circle" +msgstr "" + +#: toolbar.cpp:26 +msgid "Sketch curves from text in a TrueType font" +msgstr "" + +#: toolbar.cpp:28 +msgid "Sketch image from a file" +msgstr "" + +#: toolbar.cpp:30 +msgid "Create tangent arc at selected point" +msgstr "" + +#: toolbar.cpp:32 +msgid "Sketch cubic Bezier spline" +msgstr "" + +#: toolbar.cpp:34 +msgid "Sketch datum point" +msgstr "" + +#: toolbar.cpp:36 +msgid "Toggle construction" +msgstr "" + +#: toolbar.cpp:38 +msgid "Split lines / curves where they intersect" +msgstr "" + +#: toolbar.cpp:42 +msgid "Constrain distance / diameter / length" +msgstr "" + +#: toolbar.cpp:44 +msgid "Constrain angle" +msgstr "" + +#: toolbar.cpp:46 +msgid "Constrain to be horizontal" +msgstr "" + +#: toolbar.cpp:48 +msgid "Constrain to be vertical" +msgstr "" + +#: toolbar.cpp:50 +msgid "Constrain to be parallel or tangent" +msgstr "" + +#: toolbar.cpp:52 +msgid "Constrain to be perpendicular" +msgstr "" + +#: toolbar.cpp:54 +msgid "Constrain point on line / curve / plane / point" +msgstr "" + +#: toolbar.cpp:56 +msgid "Constrain symmetric" +msgstr "" + +#: toolbar.cpp:58 +msgid "Constrain equal length / radius / angle" +msgstr "" + +#: toolbar.cpp:60 +msgid "Constrain normals in same orientation" +msgstr "" + +#: toolbar.cpp:62 +msgid "Other supplementary angle" +msgstr "" + +#: toolbar.cpp:64 +msgid "Toggle reference dimension" +msgstr "" + +#: toolbar.cpp:68 +msgid "New group extruding active sketch" +msgstr "" + +#: toolbar.cpp:70 +msgid "New group rotating active sketch" +msgstr "" + +#: toolbar.cpp:72 +msgid "New group step and repeat rotating" +msgstr "" + +#: toolbar.cpp:74 +msgid "New group step and repeat translating" +msgstr "" + +#: toolbar.cpp:76 +msgid "New group in new workplane (thru given entities)" +msgstr "" + +#: toolbar.cpp:78 +msgid "New group in 3d" +msgstr "" + +#: toolbar.cpp:80 +msgid "New group linking / assembling file" +msgstr "" + +#: toolbar.cpp:84 +msgid "Nearest isometric view" +msgstr "" + +#: toolbar.cpp:86 +msgid "Align view to active workplane" +msgstr "" + +#: util.cpp:165 +msgctxt "title" +msgid "Error" +msgstr "" + +#: util.cpp:165 +msgctxt "title" +msgid "Message" +msgstr "" + +#: util.cpp:170 +msgctxt "button" +msgid "&OK" +msgstr "" + +#: view.cpp:78 +msgid "Scale cannot be zero or negative." +msgstr "" + +#: view.cpp:90 view.cpp:99 +msgid "Bad format: specify x, y, z" +msgstr "" diff --git a/res/shaders/edge.frag b/res/shaders/edge.frag new file mode 100644 index 0000000..2d2e8ac --- /dev/null +++ b/res/shaders/edge.frag @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// Edge rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +const float feather = 0.5; + +uniform vec4 color; +uniform float pixel; +uniform float width; +uniform float patternLen; +uniform float patternScale; +uniform sampler2D pattern; + +varying vec3 fragLoc; + +void main() { + // lookup distance texture + vec4 v = texture2D(pattern, vec2(fragLoc.z / patternScale, 0.0)); + + // decode distance value + float val = dot(v, vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 160581375.0)); + + // calculate cap + float dist = length(vec2(val * patternScale / (patternLen * width) + abs(fragLoc.x), fragLoc.y)); + + // perform antialiasing + float k = smoothstep(1.0 - 2.0 * feather * pixel / (width + feather * pixel), 1.0, abs(dist)); + + // perform alpha-test + if(k == 1.0) discard; + + // write resulting color + gl_FragColor = vec4(color.rgb, color.a * (1.0 - k)); +} diff --git a/res/shaders/edge.vert b/res/shaders/edge.vert new file mode 100644 index 0000000..3c5af3b --- /dev/null +++ b/res/shaders/edge.vert @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// Edge rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +const float feather = 0.5; + +attribute vec3 pos; +attribute vec3 loc; +attribute vec3 tgt; + +uniform mat4 modelview; +uniform mat4 projection; +uniform float width; +uniform float pixel; + +varying vec3 fragLoc; + +void main() { + // get camera direction from modelview matrix + vec3 dir = vec3(modelview[0].z, modelview[1].z, modelview[2].z); + + // calculate line contour extension basis for constant width and caps + vec3 norm = normalize(cross(tgt, dir)); + norm = normalize(norm - dir * dot(dir, norm)); + vec3 perp = normalize(cross(dir, norm)); + + // calculate line extension width considering antialiasing + float ext = width + feather * pixel; + + // extend line contour + vec3 vertex = pos; + vertex += ext * loc.x * normalize(perp); + vertex += ext * loc.y * normalize(norm); + + // write fragment location for calculating caps and antialiasing + fragLoc = loc; + + // transform resulting vertex with modelview and projection matrices + gl_Position = projection * modelview * vec4(vertex, 1.0); +} diff --git a/res/shaders/imesh.frag b/res/shaders/imesh.frag new file mode 100644 index 0000000..1e7a74b --- /dev/null +++ b/res/shaders/imesh.frag @@ -0,0 +1,12 @@ +//----------------------------------------------------------------------------- +// Indexed Mesh rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +uniform vec4 color; +uniform sampler2D texture_; + +void main() { + if(texture2D(texture_, gl_FragCoord.xy / 32.0).a < 0.5) discard; + gl_FragColor = color; +} diff --git a/res/shaders/imesh.vert b/res/shaders/imesh.vert new file mode 100644 index 0000000..5e3e59a --- /dev/null +++ b/res/shaders/imesh.vert @@ -0,0 +1,13 @@ +//----------------------------------------------------------------------------- +// Indexed Mesh rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +attribute vec3 pos; + +uniform mat4 modelview; +uniform mat4 projection; + +void main() { + gl_Position = projection * modelview * vec4(pos, 1.0); +} diff --git a/res/shaders/imesh_point.frag b/res/shaders/imesh_point.frag new file mode 100644 index 0000000..db165e1 --- /dev/null +++ b/res/shaders/imesh_point.frag @@ -0,0 +1,15 @@ +//----------------------------------------------------------------------------- +// Point rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +const float feather = 0.5; + +uniform vec4 color; +uniform float pixel; +uniform float width; + +void main() { + // Rectangular points + gl_FragColor = color; +} diff --git a/res/shaders/imesh_point.vert b/res/shaders/imesh_point.vert new file mode 100644 index 0000000..07aff1b --- /dev/null +++ b/res/shaders/imesh_point.vert @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// Point rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +const float feather = 0.5; + +attribute vec3 pos; +attribute vec2 loc; + +uniform mat4 modelview; +uniform mat4 projection; +uniform float width; +uniform float pixel; + +void main() { + // get camera vectors from modelview matrix + vec3 u = vec3(modelview[0].x, modelview[1].x, modelview[2].x); + vec3 v = vec3(modelview[0].y, modelview[1].y, modelview[2].y); + + // calculate point contour extension basis for constant width and caps + + // calculate point extension width considering antialiasing + float ext = width + feather * pixel; + + // extend point contour + vec3 vertex = pos; + vertex += ext * loc.x * normalize(u); + vertex += ext * loc.y * normalize(v); + + // transform resulting vertex with modelview and projection matrices + gl_Position = projection * modelview * vec4(vertex, 1.0); +} diff --git a/res/shaders/imesh_tex.frag b/res/shaders/imesh_tex.frag new file mode 100644 index 0000000..d3b3235 --- /dev/null +++ b/res/shaders/imesh_tex.frag @@ -0,0 +1,15 @@ +//----------------------------------------------------------------------------- +// Indexed Mesh rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +uniform vec4 color; +uniform sampler2D texture_; + +varying vec2 fragTex; + +void main() { + vec4 texColor = texture2D(texture_, fragTex); + if(texColor.a == 0.0) discard; + gl_FragColor = texColor * color; +} diff --git a/res/shaders/imesh_tex.vert b/res/shaders/imesh_tex.vert new file mode 100644 index 0000000..e048759 --- /dev/null +++ b/res/shaders/imesh_tex.vert @@ -0,0 +1,17 @@ +//----------------------------------------------------------------------------- +// Indexed Mesh rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +attribute vec3 pos; +attribute vec2 tex; + +uniform mat4 modelview; +uniform mat4 projection; + +varying vec2 fragTex; + +void main() { + fragTex = tex; + gl_Position = projection * modelview * vec4(pos, 1.0); +} diff --git a/res/shaders/imesh_texa.frag b/res/shaders/imesh_texa.frag new file mode 100644 index 0000000..89a124c --- /dev/null +++ b/res/shaders/imesh_texa.frag @@ -0,0 +1,13 @@ +//----------------------------------------------------------------------------- +// Indexed Mesh rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +uniform vec4 color; +uniform sampler2D texture_; + +varying vec2 fragTex; + +void main() { + gl_FragColor = vec4(color.rgb, color.a * texture2D(texture_, fragTex).TEX_ALPHA); +} diff --git a/res/shaders/mesh.frag b/res/shaders/mesh.frag new file mode 100644 index 0000000..8cb32c2 --- /dev/null +++ b/res/shaders/mesh.frag @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// Mesh rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +uniform vec3 lightDir0; +uniform vec3 lightDir1; +uniform float lightInt0; +uniform float lightInt1; +uniform float ambient; + +varying vec3 fragNormal; +varying vec4 fragColor; + +void main() { + vec3 result = fragColor.xyz * ambient; + vec3 normal = normalize(fragNormal); + + float light0 = clamp(dot(lightDir0, normal), 0.0, 1.0) * lightInt0 * (1.0 - ambient); + result += fragColor.rgb * light0; + + float light1 = clamp(dot(lightDir1, normal), 0.0, 1.0) * lightInt1 * (1.0 - ambient); + result += fragColor.rgb * light1; + + gl_FragColor = vec4(result, fragColor.a); +} diff --git a/res/shaders/mesh.vert b/res/shaders/mesh.vert new file mode 100644 index 0000000..c310ffc --- /dev/null +++ b/res/shaders/mesh.vert @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------------- +// Mesh rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +attribute vec3 pos; +attribute vec3 nor; +attribute vec4 col; + +uniform mat4 modelview; +uniform mat4 projection; + +varying vec3 fragNormal; +varying vec4 fragColor; + +void main() { + fragNormal = vec3(modelview * vec4(nor, 0.0)); + fragColor = col; + + gl_Position = projection * modelview * vec4(pos, 1.0); +} diff --git a/res/shaders/mesh_fill.frag b/res/shaders/mesh_fill.frag new file mode 100644 index 0000000..c8b3d37 --- /dev/null +++ b/res/shaders/mesh_fill.frag @@ -0,0 +1,12 @@ +//----------------------------------------------------------------------------- +// Mesh rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +uniform vec4 color; +uniform sampler2D texture_; + +void main() { + if(texture2D(texture_, gl_FragCoord.xy / 32.0).TEX_ALPHA < 0.5) discard; + gl_FragColor = color; +} diff --git a/res/shaders/mesh_fill.vert b/res/shaders/mesh_fill.vert new file mode 100644 index 0000000..935efa6 --- /dev/null +++ b/res/shaders/mesh_fill.vert @@ -0,0 +1,13 @@ +//----------------------------------------------------------------------------- +// Mesh rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +attribute vec3 pos; + +uniform mat4 modelview; +uniform mat4 projection; + +void main() { + gl_Position = projection * modelview * vec4(pos, 1.0); +} diff --git a/res/shaders/outline.vert b/res/shaders/outline.vert new file mode 100644 index 0000000..7683a14 --- /dev/null +++ b/res/shaders/outline.vert @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// Outline rendering shader +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +const int EMPHASIZED_AND_CONTOUR = 0; +const int EMPHASIZED_WITHOUT_CONTOUR = 1; +const int CONTOUR_ONLY = 2; + +const float feather = 0.5; + +attribute vec3 pos; +attribute vec4 loc; +attribute vec3 tgt; +attribute vec3 nol; +attribute vec3 nor; + +uniform mat4 modelview; +uniform mat4 projection; +uniform float width; +uniform float pixel; +uniform int mode; + +varying vec3 fragLoc; + +void main() { + // get camera direction from modelview matrix + vec3 dir = vec3(modelview[0].z, modelview[1].z, modelview[2].z); + + // perform outline visibility test + float ldot = dot(nol, dir); + float rdot = dot(nor, dir); + + bool isOutline = (ldot > -1e-6) == (rdot < 1e-6) || + (rdot > -1e-6) == (ldot < 1e-6); + bool isTagged = loc.w > 0.5; + + float visible = float((mode == CONTOUR_ONLY && isOutline) || + (mode == EMPHASIZED_AND_CONTOUR && (isOutline || isTagged)) || + (mode == EMPHASIZED_WITHOUT_CONTOUR && isTagged && !isOutline)); + + // calculate line contour extension basis for constant width and caps + vec3 norm = normalize(cross(tgt, dir)); + norm = normalize(norm - dir * dot(dir, norm)); + vec3 perp = normalize(cross(dir, norm)); + + // calculate line extension width considering antialiasing + float ext = (width + feather * pixel) * visible; + + // extend line contour + vec3 vertex = pos; + vertex += ext * loc.x * normalize(perp); + vertex += ext * loc.y * normalize(norm); + + // write fragment location for calculating caps and antialiasing + fragLoc = vec3(loc); + + // transform resulting vertex with modelview and projection matrices + gl_Position = projection * modelview * vec4(vertex, 1.0); +} diff --git a/res/threejs/SolveSpaceControls.js b/res/threejs/SolveSpaceControls.js new file mode 100644 index 0000000..e141ace --- /dev/null +++ b/res/threejs/SolveSpaceControls.js @@ -0,0 +1,536 @@ +window.devicePixelRatio = window.devicePixelRatio || 1; + +SolvespaceCamera = function(renderWidth, renderHeight, scale, up, right, offset) { + THREE.Camera.call(this); + + this.type = 'SolvespaceCamera'; + + this.renderWidth = renderWidth; + this.renderHeight = renderHeight; + this.zoomScale = scale; /* Avoid namespace collision w/ THREE.Object.scale */ + this.up = up; + this.right = right; + this.offset = offset; + this.depthBias = 0; + + this.updateProjectionMatrix(); +}; + +SolvespaceCamera.prototype = Object.create(THREE.Camera.prototype); +SolvespaceCamera.prototype.constructor = SolvespaceCamera; +SolvespaceCamera.prototype.updateProjectionMatrix = function() { + var temp = new THREE.Matrix4(); + var offset = new THREE.Matrix4().makeTranslation(this.offset.x, this.offset.y, this.offset.z); + // Convert to right handed- do up cross right instead. + var n = new THREE.Vector3().crossVectors(this.up, this.right); + var rotate = new THREE.Matrix4().makeBasis(this.right, this.up, n); + rotate.transpose(); + /* Transpose of rotation matrix == inverse. Rotating the camera by a + basis is equivalent to rotating an object by the inverse of the + basis. To mimic Solvespace's behavior, we pan relative to the camera. + So we need to be rotated to where the camera is pointing before panning. + See: https://en.wikipedia.org/wiki/Change_of_basis#Two_dimensions */ + + /* TODO: If we want perspective, we need an additional matrix + here which will modify w for perspective divide. */ + var scale = new THREE.Matrix4().makeScale(2 * this.zoomScale / this.renderWidth, + 2 * this.zoomScale / this.renderHeight, this.zoomScale / 30000.0); + + temp.multiply(scale); + temp.multiply(rotate); + temp.multiply(offset); + + this.projectionMatrix.copy(temp); +}; + +SolvespaceCamera.prototype.NormalizeProjectionVectors = function() { + /* After rotating, up and right may no longer be orthogonal. + However, their cross product will produce the correct + rotated plane, and we can recover an orthogonal basis. */ + var n = new THREE.Vector3().crossVectors(this.right, this.up); + this.up = new THREE.Vector3().crossVectors(n, this.right); + this.right.normalize(); + this.up.normalize(); +}; + +SolvespaceCamera.prototype.rotate = function(right, up) { + var oldRight = new THREE.Vector3().copy(this.right).normalize(); + var oldUp = new THREE.Vector3().copy(this.up).normalize(); + this.up.applyAxisAngle(oldRight, up); + this.right.applyAxisAngle(oldUp, right); + this.NormalizeProjectionVectors(); +}; + +SolvespaceCamera.prototype.offsetProj = function(right, up) { + var shift = new THREE.Vector3(right * this.right.x + up * this.up.x, + right * this.right.y + up * this.up.y, + right * this.right.z + up * this.up.z); + this.offset.add(shift); +}; + +/* Calculate the offset in terms of up and right projection vectors +that will preserve the world coordinates of the current mouse position after +the zoom. */ +SolvespaceCamera.prototype.zoomTo = function(x, y, delta) { + // Get offset components in world coordinates, in terms of up/right. + var projOffsetX = this.offset.dot(this.right); + var projOffsetY = this.offset.dot(this.up); + + /* Remove offset before scaling so, that mouse position changes + proportionally to the model and independent of current offset. */ + var centerRightI = x/this.zoomScale - projOffsetX; + var centerUpI = y/this.zoomScale - projOffsetY; + var zoomFactor; + + /* Zoom 20% every 100 delta. */ + if(delta < 0) { + zoomFactor = (-delta * 0.002 + 1); + } + else if(delta > 0) { + zoomFactor = (delta * (-1.0/600.0) + 1); + } + else { + return; + } + + this.zoomScale = this.zoomScale * zoomFactor; + var centerRightF = x/this.zoomScale - projOffsetX; + var centerUpF = y/this.zoomScale - projOffsetY; + + this.offset.addScaledVector(this.right, centerRightF - centerRightI); + this.offset.addScaledVector(this.up, centerUpF - centerUpI); +}; + +SolvespaceControls = function(object, domElement) { + var _this = this; + this.object = object; + this.domElement = ( domElement !== undefined ) ? domElement : document; + + var threePan = new Hammer.Pan({event : 'threepan', pointers : 3, enable : false}); + var panAfterTap = new Hammer.Pan({event : 'panaftertap', enable : false}); + + this.touchControls = new Hammer.Manager(domElement, { + recognizers: [ + [Hammer.Pinch, { enable: true }], + [Hammer.Pan], + [Hammer.Tap], + ] + }); + + this.touchControls.add(threePan); + this.touchControls.add(panAfterTap); + + var changeEvent = { + type: 'change' + }; + var startEvent = { + type: 'start' + }; + var endEvent = { + type: 'end' + }; + + var _changed = false; + var _offsetPrev = new THREE.Vector2(0, 0); + var _offsetCur = new THREE.Vector2(0, 0); + var _rotatePrev = new THREE.Vector2(0, 0); + var _rotateCur = new THREE.Vector2(0, 0); + + // Used during touch events. + var _rotateOrig = new THREE.Vector2(0, 0); + var _offsetOrig = new THREE.Vector2(0, 0); + var _prevScale = 1.0; + + this.handleEvent = function(event) { + if (typeof this[event.type] == 'function') { + this[event.type](event); + } + } + + function mousedown(event) { + event.preventDefault(); + event.stopPropagation(); + + switch (event.button) { + case 0: + _rotateCur.set(event.screenX, + event.screenY); + _rotatePrev.copy(_rotateCur); + document.addEventListener('mousemove', mousemove_rotate, false); + document.addEventListener('mouseup', mouseup, false); + break; + case 2: + _offsetCur.set(event.screenX / window.devicePixelRatio, + event.screenY / window.devicePixelRatio); + _offsetPrev.copy(_offsetCur); + document.addEventListener('mousemove', mousemove_pan, false); + document.addEventListener('mouseup', mouseup, false); + break; + default: + break; + } + } + + function wheel( event ) { + event.preventDefault(); + /* FIXME: Width and height might not be supported universally, but + can be calculated? */ + var box = _this.domElement.getBoundingClientRect(); + object.zoomTo(event.clientX - box.width/2 - box.left, + -(event.clientY - box.height/2 - box.top), event.deltaY); + _changed = true; + } + + function mousemove_rotate(event) { + _rotateCur.set(event.screenX, + event.screenY); + var diff = new THREE.Vector2().subVectors(_rotateCur, _rotatePrev) + .multiplyScalar(1 / object.zoomScale); + object.rotate(-0.3 * Math.PI / 180 * diff.x * object.zoomScale, + -0.3 * Math.PI / 180 * diff.y * object.zoomScale); + _changed = true; + _rotatePrev.copy(_rotateCur); + } + + function mousemove_pan(event) { + _offsetCur.set(event.screenX / window.devicePixelRatio, + event.screenY / window.devicePixelRatio); + var diff = new THREE.Vector2().subVectors(_offsetCur, _offsetPrev) + .multiplyScalar(window.devicePixelRatio / object.zoomScale); + object.offsetProj(diff.x, -diff.y); + _changed = true; + _offsetPrev.copy(_offsetCur); + } + + function mouseup(event) { + /* TODO: Opera mouse gestures will intercept this event, making it + possible to have multiple mousedown events consecutively without + a corresponding mouseup (so multiple viewports can be rotated/panned + simultaneously). Disable mouse gestures for now. */ + event.preventDefault(); + event.stopPropagation(); + + switch (event.button) { + case 0: + document.removeEventListener('mousemove', mousemove_rotate); + document.removeEventListener('mouseup', mouseup); + break; + case 2: + document.removeEventListener('mousemove', mousemove_pan); + document.removeEventListener('mouseup', mouseup); + break; + } + + _this.dispatchEvent(endEvent); + } + + function pan(event) { + /* neWcur - prev does not necessarily equal (cur + diff) - prev. + Floating point is not associative. */ + var touchDiff = new THREE.Vector2(event.deltaX, event.deltaY); + _rotateCur.addVectors(_rotateOrig, touchDiff); + var incDiff = new THREE.Vector2().subVectors(_rotateCur, _rotatePrev) + .multiplyScalar(1 / object.zoomScale); + object.rotate(-0.3 * Math.PI / 180 * incDiff.x * object.zoomScale, + -0.3 * Math.PI / 180 * incDiff.y * object.zoomScale); + _changed = true; + _rotatePrev.copy(_rotateCur); + } + + function panstart(event) { + /* TODO: Dynamically enable pan function? */ + _rotateOrig.copy(_rotateCur); + } + + function pinchstart(event) { + _prevScale = event.scale; + } + + function pinch(event) { + /* FIXME: Width and height might not be supported universally, but + can be calculated? */ + var box = _this.domElement.getBoundingClientRect(); + + /* 16.6... pixels chosen heuristically... matches my touchpad. */ + if (event.scale < _prevScale) { + object.zoomTo(event.center.x - box.width/2 - box.left, + -(event.center.y - box.height/2 - box.top), 100/6.0); + _changed = true; + } else if (event.scale > _prevScale) { + object.zoomTo(event.center.x - box.width/2 - box.left, + -(event.center.y - box.height/2 - box.top), -100/6.0); + _changed = true; + } + + _prevScale = event.scale; + } + + /* A tap will enable panning/disable rotate. */ + function tap(event) { + panAfterTap.set({enable : true}); + _this.touchControls.get('pan').set({enable : false}); + } + + function panaftertap(event) { + var touchDiff = new THREE.Vector2(event.deltaX, event.deltaY); + _offsetCur.addVectors(_offsetOrig, touchDiff); + var incDiff = new THREE.Vector2().subVectors(_offsetCur, _offsetPrev) + .multiplyScalar(1 / object.zoomScale); + object.offsetProj(incDiff.x, -incDiff.y); + _changed = true; + _offsetPrev.copy(_offsetCur); + } + + function panaftertapstart(event) { + _offsetOrig.copy(_offsetCur); + } + + function panaftertapend(event) { + panAfterTap.set({enable : false}); + _this.touchControls.get('pan').set({enable : true}); + } + + function contextmenu(event) { + event.preventDefault(); + } + + this.update = function() { + if (_changed) { + _this.dispatchEvent(changeEvent); + _changed = false; + } + }; + + this.domElement.addEventListener('mousedown', mousedown, false); + this.domElement.addEventListener('wheel', wheel, false); + this.domElement.addEventListener('contextmenu', contextmenu, false); + + /* Hammer.on wraps addEventListener */ + // Rotate + this.touchControls.on('pan', pan); + this.touchControls.on('panstart', panstart); + + // Zoom + this.touchControls.on('pinch', pinch); + this.touchControls.on('pinchstart', pinchstart); + + //Pan + this.touchControls.on('tap', tap); + this.touchControls.on('panaftertapstart', panaftertapstart); + this.touchControls.on('panaftertap', panaftertap); + this.touchControls.on('panaftertapend', panaftertapend); +}; + +SolvespaceControls.prototype = Object.create(THREE.EventDispatcher.prototype); +SolvespaceControls.prototype.constructor = SolvespaceControls; + + +solvespace = function(obj, params) { + var scene, edgeScene, camera, edgeCamera, renderer; + var geometry, controls, material, mesh, edges; + var width, height, scale, offset, projRight, projUp; + var directionalLightArray = []; + var inheritedWidth = false, inheritedHeight = false; + + if (typeof params === "undefined" || !("width" in params)) { + width = window.innerWidth; + inheritedWidth = true; + } else { + width = params.width; + } + + if (typeof params === "undefined" || !("height" in params)) { + height = window.innerHeight; + inheritedHeight = true; + } else { + height = params.height; + } + + if (typeof params === "undefined" || !("scale" in params)) { + scale = 5; + } else { + scale = params.scale; + } + + if (typeof params === "undefined" || !("offset" in params)) { + offset = new THREE.Vector3(0, 0, 0); + } else { + offset = params.offset; + } + + if (typeof params === "undefined" || !("projUp" in params)) { + projUp = new THREE.Vector3(0, 1, -1); + } else { + projUp = params.projUp; + } + + if (typeof params === "undefined" || !("projRight" in params)) { + projRight = new THREE.Vector3(1, 0, -1); + } else { + projRight = params.projRight; + } + + var domElement = init(); + lightUpdate(); + render(); + return domElement; + + function init() { + scene = new THREE.Scene(); + edgeScene = new THREE.Scene(); + + camera = new SolvespaceCamera(width, height, scale, projUp, projRight, offset); + camera.NormalizeProjectionVectors(); + + mesh = createMesh(obj); + scene.add(mesh); + edges = createEdges(obj); + edgeScene.add(edges); + + for (var i = 0; i < obj.lights.d.length; i++) { + var lightColor = new THREE.Color(obj.lights.d[i].intensity, + obj.lights.d[i].intensity, obj.lights.d[i].intensity); + var directionalLight = new THREE.DirectionalLight(lightColor, 1); + directionalLight.position.set(obj.lights.d[i].direction[0], + obj.lights.d[i].direction[1], obj.lights.d[i].direction[2]); + directionalLightArray.push(directionalLight); + scene.add(directionalLight); + } + + var lightColor = new THREE.Color(obj.lights.a, obj.lights.a, obj.lights.a); + var ambientLight = new THREE.AmbientLight(lightColor.getHex()); + scene.add(ambientLight); + + renderer = new THREE.WebGLRenderer({ antialias: true}); + renderer.setSize(width * window.devicePixelRatio, height * window.devicePixelRatio); + renderer.autoClear = false; + renderer.domElement.style = + "width: " + width + "px;" + + "height: " + height + "px;"; + + controls = new SolvespaceControls(camera, renderer.domElement); + controls.addEventListener("change", render); + controls.addEventListener("change", lightUpdate); + + if(inheritedWidth || inheritedHeight) { + window.addEventListener("resize", resize); + } + + animate(); + return renderer.domElement; + } + + function resize() { + scale = camera.zoomScale; + if(inheritedWidth) { + scale *= window.innerWidth / width; + width = window.innerWidth; + } + if(inheritedHeight) { + scale *= window.innerHeight / height; + height = window.innerHeight; + } + + camera.renderWidth = width; + camera.renderHeight = height; + camera.zoomScale = scale; + + renderer.setSize(width * window.devicePixelRatio, height * window.devicePixelRatio); + renderer.domElement.style = + "width: " + width + "px;" + + "height: " + height + "px;"; + + render(); + } + + function animate() { + requestAnimationFrame(animate); + controls.update(); + } + + function render() { + var context = renderer.getContext(); + camera.updateProjectionMatrix(); + renderer.clear(); + + context.depthRange(0.1, 1); + renderer.render(scene, camera); + + context.depthRange(0.1-(2/60000.0), 1-(2/60000.0)); + renderer.render(edgeScene, camera); + } + + function lightUpdate() { + var changeBasis = new THREE.Matrix4(); + + // The original light positions were in camera space. + // Project them into standard space using camera's basis + // vectors (up, target, and their cross product). + var n = new THREE.Vector3().crossVectors(camera.up, camera.right); + changeBasis.makeBasis(camera.right, camera.up, n); + + for (var i = 0; i < 2; i++) { + var newLightPos = changeBasis.applyToVector3Array( + [obj.lights.d[i].direction[0], obj.lights.d[i].direction[1], + obj.lights.d[i].direction[2]]); + directionalLightArray[i].position.set(newLightPos[0], + newLightPos[1], newLightPos[2]); + } + } + + function createMesh(meshObj) { + var geometry = new THREE.Geometry(); + var materialIndex = 0; + var materialList = []; + var opacitiesSeen = {}; + + for (var i = 0; i < meshObj.points.length; i++) { + geometry.vertices.push(new THREE.Vector3(meshObj.points[i][0], + meshObj.points[i][1], meshObj.points[i][2])); + } + + for (var i = 0; i < meshObj.faces.length; i++) { + var currOpacity = ((meshObj.colors[i] & 0xFF000000) >>> 24) / 255.0; + if (opacitiesSeen[currOpacity] === undefined) { + opacitiesSeen[currOpacity] = materialIndex; + materialIndex++; + materialList.push(new THREE.MeshLambertMaterial({ + vertexColors: THREE.FaceColors, + opacity: currOpacity, + transparent: true, + side: THREE.DoubleSide + })); + } + + geometry.faces.push(new THREE.Face3(meshObj.faces[i][0], + meshObj.faces[i][1], meshObj.faces[i][2], + [new THREE.Vector3(meshObj.normals[i][0][0], + meshObj.normals[i][0][1], meshObj.normals[i][0][2]), + new THREE.Vector3(meshObj.normals[i][1][0], + meshObj.normals[i][1][1], meshObj.normals[i][1][2]), + new THREE.Vector3(meshObj.normals[i][2][0], + meshObj.normals[i][2][1], meshObj.normals[i][2][2])], + new THREE.Color(meshObj.colors[i] & 0x00FFFFFF), + opacitiesSeen[currOpacity])); + } + + geometry.computeBoundingSphere(); + return new THREE.Mesh(geometry, new THREE.MultiMaterial(materialList)); + } + + function createEdges(meshObj) { + var geometry = new THREE.Geometry(); + var material = new THREE.LineBasicMaterial(); + + for (var i = 0; i < meshObj.edges.length; i++) { + geometry.vertices.push(new THREE.Vector3(meshObj.edges[i][0][0], + meshObj.edges[i][0][1], meshObj.edges[i][0][2]), + new THREE.Vector3(meshObj.edges[i][1][0], + meshObj.edges[i][1][1], meshObj.edges[i][1][2])); + } + + geometry.computeBoundingSphere(); + return new THREE.LineSegments(geometry, material); + } +}; + diff --git a/res/threejs/hammer-2.0.8.js.gz b/res/threejs/hammer-2.0.8.js.gz new file mode 100644 index 0000000000000000000000000000000000000000..764a580fa9dc21548397ffa5189cfc43a462394d GIT binary patch literal 17503 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

wF@RB5oZrwJD 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 - + Parametric 3d CAD tool. + + + PerMonitorV2, PerMonitor + true + + diff --git a/res/win32/versioninfo.rc.in b/res/win32/versioninfo.rc.in new file mode 100644 index 0000000..0772a39 --- /dev/null +++ b/res/win32/versioninfo.rc.in @@ -0,0 +1,29 @@ +1 VERSIONINFO +FILEVERSION ${solvespace_VERSION_MAJOR},${solvespace_VERSION_MINOR},0,0 +PRODUCTVERSION ${solvespace_VERSION_MAJOR},${solvespace_VERSION_MINOR},0,0 +FILEFLAGSMASK 0 +FILEFLAGS 0 +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE 0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "04090000" + BEGIN + VALUE "CompanyName", "The SolveSpace authors" + VALUE "ProductName", "SolveSpace" + VALUE "ProductVersion", "${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}" + VALUE "FileDescription", "SolveSpace, a parametric 2d/3d CAD" + VALUE "FileVersion", "${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR}~${solvespace_GIT_HASH}" + VALUE "OriginalFilename", "solvespace.exe" + VALUE "InternalName", "solvespace" + VALUE "LegalCopyright", "(c) 2008-2016 Jonathan Westhues and other authors" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0 + END +END diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5c53130..1a46e24 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,42 +1,29 @@ -# global - include(GNUInstallDirs) -include_directories( - ${OPENGL_INCLUDE_DIR} - ${PNG_INCLUDE_DIRS} - ${FREETYPE_INCLUDE_DIRS}) - -link_directories( - ${PNG_LIBRARY_DIRS} - ${FREETYPE_LIBRARY_DIRS}) - -add_definitions( - ${PNG_CFLAGS_OTHER}) +# configuration include_directories( ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/built ${CMAKE_CURRENT_BINARY_DIR}) -if(SPACEWARE_FOUND) - include_directories( - ${SPACEWARE_INCLUDE_DIR}) +set(HAVE_SPACEWARE ${SPACEWARE_FOUND}) + +if(NOT WIN32 OR APPLE) + if(GTKMM_gtkmm-3.0_VERSION VERSION_LESS "3.24.0") + set(HAVE_GTK_FILECHOOSERNATIVE 0) + else() + set(HAVE_GTK_FILECHOOSERNATIVE 1) + endif() endif() -set(HAVE_SPACEWARE ${SPACEWARE_FOUND}) -set(HAVE_GTK ${GTKMM_FOUND}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) # platform utilities -if(WIN32) - set(util_SOURCES - win32/w32util.cpp) -else() - set(util_SOURCES - unix/unixutil.cpp) +if(APPLE) + set(util_LIBRARIES + ${APPKIT_LIBRARY}) endif() # libslvs @@ -47,10 +34,12 @@ set(libslvs_SOURCES expr.cpp constraint.cpp constrainteq.cpp - system.cpp) + system.cpp + platform/platform.cpp) set(libslvs_HEADERS - solvespace.h) + solvespace.h + platform/platform.h) add_library(slvs SHARED ${libslvs_SOURCES} @@ -64,6 +53,13 @@ target_compile_definitions(slvs target_include_directories(slvs PUBLIC ${CMAKE_SOURCE_DIR}/include) +target_link_libraries(slvs + ${util_LIBRARIES} + mimalloc-static) + +add_dependencies(slvs + mimalloc-static) + set_target_properties(slvs PROPERTIES PUBLIC_HEADER ${CMAKE_SOURCE_DIR}/include/slvs.h VERSION ${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR} @@ -72,198 +68,93 @@ set_target_properties(slvs PROPERTIES if(NOT WIN32) install(TARGETS slvs LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) endif() -# generated files - -# `$` allows us to use binfmt support on Linux -# without special-casing anything; running `tool.exe` would succeed -# but unlike Windows, Linux does not have the machinery to map -# an invocation of `tool` to an executable `tool.exe` in $PATH. - -file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/generated) - -file(GLOB icons ${CMAKE_CURRENT_SOURCE_DIR}/icons/*.png) -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated/icons.cpp - ${CMAKE_CURRENT_BINARY_DIR}/generated/icons.h - COMMAND $ - ${CMAKE_CURRENT_BINARY_DIR}/generated/icons.cpp - ${CMAKE_CURRENT_BINARY_DIR}/generated/icons.h - ${icons} - DEPENDS png2c ${icons} - VERBATIM) - -file(GLOB chars ${CMAKE_CURRENT_SOURCE_DIR}/fonts/private/*.png) -list(SORT chars) -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated/bitmapfont.table.h - COMMAND $ - ${CMAKE_CURRENT_BINARY_DIR}/generated/bitmapfont.table.h - ${CMAKE_CURRENT_SOURCE_DIR}/fonts/unifont-8.0.01.hex.gz - ${chars} - DEPENDS unifont2c - ${CMAKE_CURRENT_SOURCE_DIR}/fonts/unifont-8.0.01.hex.gz - ${chars} - VERBATIM) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h - COMMAND $ - ${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h - ${CMAKE_CURRENT_SOURCE_DIR}/fonts/unicode.lff.gz - DEPENDS lff2c - ${CMAKE_CURRENT_SOURCE_DIR}/fonts/unicode.lff.gz - VERBATIM) - -set(generated_HEADERS - ${CMAKE_CURRENT_BINARY_DIR}/generated/vectorfont.table.h - ${CMAKE_CURRENT_BINARY_DIR}/generated/bitmapfont.table.h - ${CMAKE_CURRENT_BINARY_DIR}/generated/icons.h) - -set(generated_SOURCES - ${CMAKE_CURRENT_BINARY_DIR}/generated/icons.cpp) - -# platform dependencies - -if(WIN32) - set(platform_SOURCES - win32/w32main.cpp - win32/resource.rc) - - set(platform_LIBRARIES - comctl32) -elseif(APPLE) - add_definitions( - -fobjc-arc) - - set(platform_SOURCES - cocoa/cocoamain.mm - unix/gloffscreen.cpp) - - set(platform_XIBS - cocoa/MainMenu.xib - cocoa/SaveFormatAccessory.xib) +# solvespace dependencies - set(platform_ICONS - cocoa/AppIcon.iconset) - - set(platform_RESOURCES - unix/solvespace-48x48.png) - - set(platform_BUNDLED_LIBS - ${PNG_LIBRARIES} - ${FREETYPE_LIBRARIES}) - - set(platform_LIBRARIES - ${APPKIT_LIBRARY}) -elseif(HAVE_GTK) +include_directories( + ${OPENGL_INCLUDE_DIR} + ${ZLIB_INCLUDE_DIR} + ${PNG_PNG_INCLUDE_DIR} + ${FREETYPE_INCLUDE_DIRS} + ${CAIRO_INCLUDE_DIRS} + ${Q3D_INCLUDE_DIR} + ${MIMALLOC_INCLUDE_DIR}) + +if(Backtrace_FOUND) include_directories( - ${GTKMM_INCLUDE_DIRS} - ${JSONC_INCLUDE_DIRS} - ${FONTCONFIG_INCLUDE_DIRS} - ${GLEW_INCLUDE_DIRS}) - - link_directories( - ${GTKMM_LIBRARY_DIRS} - ${JSONC_LIBRARY_DIRS} - ${FONTCONFIG_LIBRARY_DIRS} - ${GLEW_LIBRARY_DIRS}) - - add_definitions( - ${GTKMM_CFLAGS_OTHER} - ${JSONC_CFLAGS_OTHER} - ${FONTCONFIG_CFLAGS_OTHER} - ${GLEW_CFLAGS_OTHER}) - - set(platform_SOURCES - gtk/gtkmain.cpp - unix/gloffscreen.cpp) - - set(platform_LIBRARIES - ${GTKMM_LIBRARIES} - ${JSONC_LIBRARIES} - ${FONTCONFIG_LIBRARIES} - ${GLEW_LIBRARIES}) + ${Backtrace_INCLUDE_DIRS}) endif() -set(platform_BUNDLED_RESOURCES) +if(SPACEWARE_FOUND) + include_directories( + ${SPACEWARE_INCLUDE_DIR}) +endif() -foreach(xib ${platform_XIBS}) - get_filename_component(nib ${xib} NAME_WE) - set(source ${CMAKE_CURRENT_SOURCE_DIR}/${xib}) - set(target ${CMAKE_CURRENT_BINARY_DIR}/solvespace.app/Contents/Resources/${nib}.nib) - list(APPEND platform_BUNDLED_RESOURCES ${target}) +if(OPENGL STREQUAL 3) + set(gl_SOURCES + render/gl3shader.cpp + render/rendergl3.cpp) +elseif(OPENGL STREQUAL 1) + set(gl_SOURCES + render/rendergl1.cpp) +else() + message(FATAL_ERROR "Unsupported OpenGL version ${OPENGL}") +endif() - add_custom_command( - OUTPUT ${target} - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/solvespace.app/Contents/Resources - COMMAND ibtool --errors --warnings --notices - --output-format human-readable-text --compile - ${target} ${source} - COMMENT "Building Interface Builder file ${xib}" - DEPENDS ${xib} - VERBATIM) -endforeach() +set(platform_SOURCES + ${gl_SOURCES} + platform/entrygui.cpp) -foreach(icon ${platform_ICONS}) - get_filename_component(name ${icon} NAME_WE) - set(source ${CMAKE_CURRENT_SOURCE_DIR}/${icon}) - set(target ${CMAKE_CURRENT_BINARY_DIR}/solvespace.app/Contents/Resources/${name}.icns) - list(APPEND platform_BUNDLED_RESOURCES ${target}) +if(WIN32) + list(APPEND platform_SOURCES + platform/guiwin.cpp) - add_custom_command( - OUTPUT ${target} - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/solvespace.app/Contents/Resources - COMMAND iconutil -c icns -o ${target} ${source} - COMMENT "Building icon set ${icon}" - DEPENDS ${source} - VERBATIM) -endforeach() + set(platform_LIBRARIES + comctl32 + ${SPACEWARE_LIBRARIES}) +elseif(APPLE) + add_compile_options( + -fobjc-arc) -foreach(res ${platform_RESOURCES}) - get_filename_component(name ${res} NAME) - set(source ${CMAKE_CURRENT_SOURCE_DIR}/${res}) - set(target ${CMAKE_CURRENT_BINARY_DIR}/solvespace.app/Contents/Resources/${name}) - list(APPEND platform_BUNDLED_RESOURCES ${target}) + list(APPEND platform_SOURCES + platform/guimac.mm) +else() + list(APPEND platform_SOURCES + platform/guigtk.cpp) - add_custom_command( - OUTPUT ${target} - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/solvespace.app/Contents/Resources - COMMAND ${CMAKE_COMMAND} -E copy ${source} ${target} - COMMENT "Copying resource file ${res}" - DEPENDS ${res} - VERBATIM) -endforeach() + set(platform_LIBRARIES + ${SPACEWARE_LIBRARIES}) -foreach(lib ${platform_BUNDLED_LIBS}) - get_filename_component(name ${lib} NAME) - set(target ${CMAKE_CURRENT_BINARY_DIR}/solvespace.app/Contents/MacOS/${name}) - list(APPEND platform_BUNDLED_RESOURCES ${target}) + foreach(pkg_config_lib GTKMM JSONC FONTCONFIG) + include_directories(${${pkg_config_lib}_INCLUDE_DIRS}) + link_directories(${${pkg_config_lib}_LIBRARY_DIRS}) + list(APPEND platform_LIBRARIES ${${pkg_config_lib}_LIBRARIES}) + endforeach() +endif() - add_custom_command( - OUTPUT ${target} - COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/solvespace.app/Contents/Resources - COMMAND ${CMAKE_COMMAND} -E copy ${lib} ${target} - COMMENT "Bundling shared library ${lib}" - DEPENDS ${lib} - VERBATIM) -endforeach() +set(every_platform_SOURCES + platform/guiwin.cpp + platform/guigtk.cpp + platform/guimac.mm) -# solvespace executable +# solvespace library -set(solvespace_HEADERS - config.h +set(solvespace_core_HEADERS dsc.h expr.h polygon.h sketch.h solvespace.h ui.h + platform/platform.h + render/render.h + render/gl3shader.h srf/surface.h) -set(solvespace_SOURCES +set(solvespace_core_SOURCES bsp.cpp clipboard.cpp confscreen.cpp @@ -280,17 +171,18 @@ set(solvespace_SOURCES expr.cpp file.cpp generate.cpp - glhelper.cpp graphicswin.cpp group.cpp groupmesh.cpp importdxf.cpp + importidf.cpp mesh.cpp modify.cpp mouse.cpp + polyline.cpp polygon.cpp + resource.cpp request.cpp - solvespace.cpp style.cpp system.cpp textscreens.cpp @@ -300,6 +192,10 @@ set(solvespace_SOURCES undoredo.cpp util.cpp view.cpp + platform/platform.cpp + platform/gui.cpp + render/render.cpp + render/render2d.cpp srf/boolean.cpp srf/curve.cpp srf/merge.cpp @@ -309,98 +205,214 @@ set(solvespace_SOURCES srf/surfinter.cpp srf/triangulate.cpp) -add_executable(solvespace WIN32 MACOSX_BUNDLE - ${libslvs_HEADERS} - ${libslvs_SOURCES} +set(solvespace_core_gl_SOURCES + solvespace.cpp) + +add_library(solvespace-core STATIC ${util_SOURCES} - ${platform_SOURCES} - ${platform_BUNDLED_RESOURCES} - ${generated_SOURCES} - ${generated_HEADERS} - ${solvespace_HEADERS} - ${solvespace_SOURCES}) - -target_link_libraries(solvespace + ${solvespace_core_HEADERS} + ${solvespace_core_SOURCES}) + +add_dependencies(solvespace-core + q3d_header + mimalloc-static) + +target_link_libraries(solvespace-core + ${OpenMP_CXX_LIBRARIES} dxfrw - ${OPENGL_LIBRARIES} - ${PNG_LIBRARIES} - ${ZLIB_LIBRARIES} - ${FREETYPE_LIBRARIES} - ${platform_LIBRARIES}) - -if(WIN32 AND NOT MINGW) - set_target_properties(solvespace PROPERTIES - LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO") + ${util_LIBRARIES} + ${ZLIB_LIBRARY} + ${PNG_LIBRARY} + ${FREETYPE_LIBRARY} + flatbuffers + mimalloc-static) + +if(Backtrace_FOUND) + target_link_libraries(solvespace-core + ${Backtrace_LIBRARY}) endif() -if(SPACEWARE_FOUND) - target_link_libraries(solvespace - ${SPACEWARE_LIBRARIES}) -endif() +target_compile_options(solvespace-core + PRIVATE ${COVERAGE_FLAGS}) -if(APPLE) - set(fixups) - foreach(lib ${platform_BUNDLED_LIBS}) - get_filename_component(name ${lib} NAME) - execute_process(COMMAND otool -D ${lib} - OUTPUT_VARIABLE canonical_lib OUTPUT_STRIP_TRAILING_WHITESPACE) - string(REGEX REPLACE "^.+:\n" "" canonical_lib ${canonical_lib}) - add_custom_command(TARGET solvespace POST_BUILD - COMMAND install_name_tool -change ${canonical_lib} @executable_path/${name} - $ - COMMENT "Fixing up rpath for dylib ${name}" - VERBATIM) +# solvespace translations + +if(HAVE_GETTEXT) + set(inputs + ${solvespace_core_SOURCES} + ${solvespace_core_HEADERS} + ${every_platform_SOURCES}) + + set(templ_po ${CMAKE_CURRENT_BINARY_DIR}/../res/messages.po) + + set(output_pot ${CMAKE_CURRENT_SOURCE_DIR}/../res/messages.pot) + set(output_po ${CMAKE_CURRENT_SOURCE_DIR}/../res/locales/en_US.po) + file(GLOB locale_pos ${CMAKE_CURRENT_SOURCE_DIR}/../res/locales/*.po) + + string(REPLACE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} + gen_output_pot ${output_pot}.gen) + string(REPLACE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} + gen_output_po ${output_po}.gen) + foreach(locale_po ${locale_pos}) + string(REPLACE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} + gen_locale_po ${locale_po}.gen) + list(APPEND gen_locale_pos ${gen_locale_po}) endforeach() - set(bundle solvespace) - add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/${bundle}.dmg - COMMAND ${CMAKE_COMMAND} -E remove ${CMAKE_BINARY_DIR}/${bundle}.dmg - COMMAND hdiutil create -srcfolder ${CMAKE_CURRENT_BINARY_DIR}/${bundle}.app - ${CMAKE_BINARY_DIR}/${bundle}.dmg - DEPENDS $ - COMMENT "Building ${bundle}.dmg" + add_custom_command( + OUTPUT ${gen_output_pot} + COMMAND ${XGETTEXT} + --language=C++ + --keyword --keyword=_ --keyword=N_ --keyword=C_:2,1c --keyword=CN_:2,1c + --force-po --width=100 --sort-by-file + --package-name=SolveSpace + --package-version=${solvespace_VERSION_MAJOR}.${solvespace_VERSION_MINOR} + "--copyright-holder=the PACKAGE authors" + --msgid-bugs-address=whitequark@whitequark.org + --from-code=utf-8 --output=${gen_output_pot} ${inputs} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_output_pot} ${output_pot} + DEPENDS ${inputs} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Extracting translations" + VERBATIM) + + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../res/locales) + + # en_US is a bit special; we pre-fill the msgstrs from msgids, instead of (as would normally + # happen) leaving them empty. + add_custom_command( + OUTPUT ${gen_output_po} + COMMAND ${MSGINIT} + --locale=en_US --no-translator + --output=${templ_po} --input=${gen_output_pot} + COMMAND ${MSGMERGE} + --force-po --no-fuzzy-matching + --output=${gen_output_po} ${output_po} ${templ_po} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_output_po} ${output_po} + DEPENDS ${gen_output_pot} + COMMENT "Updating en_US translations" VERBATIM) - add_custom_target(${bundle}-dmg ALL - DEPENDS ${CMAKE_BINARY_DIR}/${bundle}.dmg) + foreach(locale_po ${locale_pos}) + string(REPLACE ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} + gen_locale_po ${locale_po}.gen) + + get_filename_component(locale_name ${locale_po} NAME_WE) + if(locale_name STREQUAL "en_US") + continue() + endif() + + add_custom_command( + OUTPUT ${gen_locale_po} + COMMAND ${MSGMERGE} + --no-fuzzy-matching + --output=${gen_locale_po} ${locale_po} ${gen_output_pot} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${gen_locale_po} ${locale_po} + DEPENDS ${gen_output_pot} + COMMENT "Updating ${locale_name} translations" + VERBATIM) + endforeach() + + add_custom_target(translate_solvespace + DEPENDS ${gen_output_pot} ${gen_output_po} ${gen_locale_pos}) endif() -if(NOT WIN32) - install(TARGETS solvespace - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - BUNDLE DESTINATION .) +# solvespace graphical executable + +if(ENABLE_GUI) + add_executable(solvespace WIN32 MACOSX_BUNDLE + ${solvespace_core_gl_SOURCES} + ${platform_SOURCES} + $) + + add_dependencies(solvespace + resources) + + target_link_libraries(solvespace + solvespace-core + ${OPENGL_LIBRARIES} + ${platform_LIBRARIES} + ${COVERAGE_LIBRARY}) + + if(MSVC) + set_target_properties(solvespace PROPERTIES + LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /INCREMENTAL:NO /OPT:REF") + elseif(APPLE) + set_target_properties(solvespace PROPERTIES + OUTPUT_NAME SolveSpace) + endif() +endif() + +# solvespace headless library + +set(headless_SOURCES + platform/guinone.cpp + render/rendercairo.cpp) + +add_library(solvespace-headless STATIC EXCLUDE_FROM_ALL + ${solvespace_core_gl_SOURCES} + ${headless_SOURCES}) + +target_compile_definitions(solvespace-headless + PRIVATE -DHEADLESS) + +target_include_directories(solvespace-headless + INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(solvespace-headless + solvespace-core + ${CAIRO_LIBRARIES}) + +target_compile_options(solvespace-headless + PRIVATE ${COVERAGE_FLAGS}) + +# solvespace command-line executable + +if(ENABLE_CLI) + add_executable(solvespace-cli + platform/entrycli.cpp + $) + + target_link_libraries(solvespace-cli + solvespace-core + solvespace-headless) + + add_dependencies(solvespace-cli + resources) + + if(MSVC) + set_target_properties(solvespace-cli PROPERTIES + LINK_FLAGS "/INCREMENTAL:NO /OPT:REF") + endif() endif() -install(FILES unix/solvespace.desktop - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications) -foreach(SIZE 16x16 24x24 32x32 48x48) - install(FILES unix/solvespace-${SIZE}.png - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}/apps - RENAME solvespace.png) - install(FILES unix/solvespace-${SIZE}.png - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}/mimetypes - RENAME application.x-solvespace.png) -endforeach() -foreach(SIZE 16x16 24x24 32x32 48x48) - install(FILES unix/solvespace-${SIZE}.xpm - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pixmaps/) -endforeach() - -# valgrind - -add_custom_target(solvespace-valgrind - valgrind - --tool=memcheck - --verbose - --track-fds=yes - --log-file=vg.%p.out - --num-callers=50 - --error-limit=no - --read-var-info=yes - --leak-check=full - --leak-resolution=high - --show-reachable=yes - --track-origins=yes - --malloc-fill=0xac - --free-fill=0xde - $) +# solvespace unix package + +if(NOT (WIN32 OR APPLE)) + if(ENABLE_GUI) + install(TARGETS solvespace + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif() + if(ENABLE_CLI) + install(TARGETS solvespace-cli + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif() +endif() + +# solvespace macOS package + +if(APPLE) + set(bundle SolveSpace) + set(bundle_bin ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/MacOS) + set(bundle_resources ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/Resources/lib) + execute_process( + COMMAND mkdir -p ${bundle_resources} + COMMAND cp -p /usr/local/opt/libomp/lib/libomp.dylib ${bundle_resources}/libomp.dylib + ) + add_custom_command(TARGET solvespace POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory ${bundle_bin} + COMMAND ${CMAKE_COMMAND} -E copy $ ${bundle_bin} + COMMAND install_name_tool -change /usr/local/opt/libomp/lib/libomp.dylib "@executable_path/../Resources/lib/libomp.dylib" ${bundle_bin}/${bundle} + COMMENT "Bundling executable solvespace-cli" + VERBATIM) +endif() diff --git a/src/bsp.cpp b/src/bsp.cpp index bba7265..bac5ff3 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -8,38 +8,31 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -SBsp2 *SBsp2::Alloc(void) { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); } -SBsp3 *SBsp3::Alloc(void) { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); } - -SBsp3 *SBsp3::FromMesh(SMesh *m) { - SBsp3 *bsp3 = NULL; - int i; +SBsp2 *SBsp2::Alloc() { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); } +SBsp3 *SBsp3::Alloc() { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); } +SBsp3 *SBsp3::FromMesh(const SMesh *m) { SMesh mc = {}; - for(i = 0; i < m->l.n; i++) { - mc.AddTriangle(&(m->l.elem[i])); - } + for(auto const &elt : m->l) { mc.AddTriangle(&elt); } srand(0); // Let's be deterministic, at least! int n = mc.l.n; while(n > 1) { int k = rand() % n; n--; - swap(mc.l.elem[k], mc.l.elem[n]); - } - - for(i = 0; i < mc.l.n; i++) { - bsp3 = InsertOrCreate(bsp3, &(mc.l.elem[i]), NULL); + swap(mc.l[k], mc.l[n]); } + SBsp3 *bsp3 = NULL; + for(auto &elt : mc.l) { bsp3 = InsertOrCreate(bsp3, &elt, NULL); } mc.Clear(); return bsp3; } -Vector SBsp3::IntersectionWith(Vector a, Vector b) { +Vector SBsp3::IntersectionWith(Vector a, Vector b) const { double da = a.Dot(n) - d; double db = b.Dot(n) - d; - if(da*db > 0) oops(); + ssassert(da*db < 0, "Expected segment to intersect BSP node"); double dab = (db - da); return (a.ScaledBy(db/dab)).Plus(b.ScaledBy(-da/dab)); @@ -69,32 +62,35 @@ void SBsp3::InsertInPlane(bool pos2, STriangle *tr, SMesh *m) { ll = ll->more; } - if(m->flipNormal && ((!pos2 && !onFace) || - (onFace && !sameNormal && m->keepCoplanar))) - { - m->AddTriangle(tr->meta, tr->c, tr->b, tr->a); - } else if(!(m->flipNormal) && ((pos2 && !onFace) || - (onFace && sameNormal && m->keepCoplanar))) - { - m->AddTriangle(tr->meta, tr->a, tr->b, tr->c); + if((!onFace && ((m->keepInsideOtherShell && !pos2) || + (!m->keepInsideOtherShell && pos2))) || + (onFace && ((m->keepCoplanar && m->flipNormal && !sameNormal) || + (m->keepCoplanar && !m->flipNormal && sameNormal)))) { + // We have decided that we need to keep a triangle either inside, + // outside or on the other shell. So add it and flip it if requested. + if(!(m->flipNormal)) { + m->AddTriangle(tr->meta, tr->a, tr->b, tr->c); + } else { + m->AddTriangle(tr->meta, tr->c, tr->b, tr->a); + } } else { m->atLeastOneDiscarded = true; } } -void SBsp3::InsertHow(int how, STriangle *tr, SMesh *instead) { +void SBsp3::InsertHow(BspClass how, STriangle *tr, SMesh *instead) { switch(how) { - case POS: + case BspClass::POS: if(instead && !pos) goto alt; pos = InsertOrCreate(pos, tr, instead); break; - case NEG: + case BspClass::NEG: if(instead && !neg) goto alt; neg = InsertOrCreate(neg, tr, instead); break; - case COPLANAR: { + case BspClass::COPLANAR: { if(instead) goto alt; SBsp3 *m = Alloc(); m->n = n; @@ -104,171 +100,348 @@ void SBsp3::InsertHow(int how, STriangle *tr, SMesh *instead) { more = m; break; } - default: oops(); } return; alt: - if(how == POS && !(instead->flipNormal)) { - instead->AddTriangle(tr->meta, tr->a, tr->b, tr->c); - } else if(how == NEG && instead->flipNormal) { - instead->AddTriangle(tr->meta, tr->c, tr->b, tr->a); - } else if(how == COPLANAR) { + if(((BspClass::POS == how) && !instead->keepInsideOtherShell) || + ((BspClass::NEG == how) && instead->keepInsideOtherShell)) { + // We have decided that we need to keep a triangle (either inside or + // outside the other shell. So add it and flip it if requested. + if(!(instead->flipNormal)) { + instead->AddTriangle(tr->meta, tr->a, tr->b, tr->c); + } else { + instead->AddTriangle(tr->meta, tr->c, tr->b, tr->a); + } + } else if(how == BspClass::COPLANAR) { if(edges) { edges->InsertTriangle(tr, instead, this); } else { // I suppose this actually is allowed to happen, if the coplanar // face is the leaf, and all of its neighbors are earlier in tree? - InsertInPlane(false, tr, instead); + InsertInPlane(/*pos2=*/false, tr, instead); } } else { instead->atLeastOneDiscarded = true; } } -void SBsp3::InsertConvexHow(int how, STriMeta meta, Vector *vertex, int n, - SMesh *instead) -{ - switch(how) { - case POS: - if(pos) { - pos = pos->InsertConvex(meta, vertex, n, instead); - return; - } - break; +class BspUtil { +public: + SBsp3 *bsp; - case NEG: - if(neg) { - neg = neg->InsertConvex(meta, vertex, n, instead); - return; - } - break; + size_t onc; + size_t posc; + size_t negc; + bool *isPos; + bool *isNeg; + bool *isOn; + + // triangle operations + STriangle *tr; + STriangle *btri; // also as alone + STriangle *ctri; - default: oops(); + // convex operations + Vector *on; + size_t npos; + size_t nneg; + Vector *vpos; // also as quad + Vector *vneg; + + static BspUtil *Alloc() { + return (BspUtil *)AllocTemporary(sizeof(BspUtil)); } - int i; - for(i = 0; i < n - 2; i++) { - STriangle tr = STriangle::From(meta, - vertex[0], vertex[i+1], vertex[i+2]); - InsertHow(how, &tr, instead); + + void AllocOn() { + on = (Vector *)AllocTemporary(sizeof(Vector) * 2); } -} -SBsp3 *SBsp3::InsertConvex(STriMeta meta, Vector *vertex, int cnt, - SMesh *instead) -{ - Vector e01 = (vertex[1]).Minus(vertex[0]); - Vector e12 = (vertex[2]).Minus(vertex[1]); - Vector out = e01.Cross(e12); - -#define MAX_VERTICES 50 - if(cnt+1 >= MAX_VERTICES) goto triangulate; - - int i; - Vector on[2]; - bool isPos[MAX_VERTICES]; - bool isNeg[MAX_VERTICES]; - bool isOn[MAX_VERTICES]; - int posc, negc, onc; posc = negc = onc = 0; - for(i = 0; i < cnt; i++) { - double dt = n.Dot(vertex[i]); - isPos[i] = isNeg[i] = isOn[i] = false; - if(fabs(dt - d) < LENGTH_EPS) { - isOn[i] = true; - if(onc < 2) { - on[onc] = vertex[i]; + void AllocTriangle() { + btri = (STriangle *)AllocTemporary(sizeof(STriangle)); + } + + void AllocTriangles() { + btri = (STriangle *)AllocTemporary(sizeof(STriangle) * 2); + ctri = &btri[1]; + } + + void AllocQuad() { + vpos = (Vector *)AllocTemporary(sizeof(Vector) * 4); + } + + void AllocClassify(size_t size) { + // Allocate a one big piece is faster than a small ones. + isPos = (bool *)AllocTemporary(sizeof(bool) * size * 3); + isNeg = &isPos[size]; + isOn = &isNeg[size]; + } + + void AllocVertices(size_t size) { + vpos = (Vector *)AllocTemporary(sizeof(Vector) * size * 2); + vneg = &vpos[size]; + } + + void ClassifyTriangle(STriangle *tri, SBsp3 *node) { + tr = tri; + bsp = node; + onc = 0; + posc = 0; + negc = 0; + + AllocClassify(3); + + double dt[3] = { (tr->a).Dot(bsp->n), (tr->b).Dot(bsp->n), (tr->c).Dot(bsp->n) }; + double d = bsp->d; + // Count vertices in the plane + for(int i = 0; i < 3; i++) { + if(dt[i] > d + LENGTH_EPS) { + posc++; + isPos[i] = true; + } else if(dt[i] < d - LENGTH_EPS) { + negc++; + isNeg[i] = true; + } else { + onc++; + isOn[i] = true; } - onc++; - } else if(dt > d) { - isPos[i] = true; - posc++; - } else { - isNeg[i] = true; - negc++; } } - if(onc != 2 && onc != 1 && onc != 0) goto triangulate; - if(onc == 2) { - if(!instead) { - SEdge se = SEdge::From(on[0], on[1]); - edges = SBsp2::InsertOrCreateEdge(edges, &se, n, out); + bool ClassifyConvex(Vector *vertex, size_t cnt, SBsp3 *node, bool insertEdge) { + bsp = node; + onc = 0; + posc = 0; + negc = 0; + + AllocClassify(cnt); + AllocOn(); + + for(size_t i = 0; i < cnt; i++) { + double dt = bsp->n.Dot(vertex[i]); + isPos[i] = isNeg[i] = isOn[i] = false; + if(fabs(dt - bsp->d) < LENGTH_EPS) { + isOn[i] = true; + if(onc < 2) { + on[onc] = vertex[i]; + } + onc++; + } else if(dt > bsp->d) { + isPos[i] = true; + posc++; + } else { + isNeg[i] = true; + negc++; + } } - } - if(posc == 0) { - InsertConvexHow(NEG, meta, vertex, cnt, instead); - return this; - } - if(negc == 0) { - InsertConvexHow(POS, meta, vertex, cnt, instead); - return this; + if(onc != 2 && onc != 1 && onc != 0) return false; + if(onc == 2) { + if(insertEdge) { + Vector e01 = (vertex[1]).Minus(vertex[0]); + Vector e12 = (vertex[2]).Minus(vertex[1]); + Vector out = e01.Cross(e12); + SEdge se = SEdge::From(on[0], on[1]); + bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, out); + } + } + return true; } - Vector vpos[MAX_VERTICES]; - Vector vneg[MAX_VERTICES]; - int npos, nneg; npos = nneg = 0; + bool ClassifyConvexVertices(Vector *vertex, size_t cnt, bool insertEdges) { + Vector inter[2]; + int inters = 0; + + npos = 0; + nneg = 0; - Vector inter[2]; - int inters; inters = 0; + // Enlarge vertices list to consider two intersections + AllocVertices(cnt + 4); - for(i = 0; i < cnt; i++) { - int ip = WRAP((i + 1), cnt); + for(size_t i = 0; i < cnt; i++) { + size_t ip = WRAP((i + 1), cnt); - if(isPos[i]) { - vpos[npos++] = vertex[i]; + if(isPos[i]) { + vpos[npos++] = vertex[i]; + } + if(isNeg[i]) { + vneg[nneg++] = vertex[i]; + } + if(isOn[i]) { + vneg[nneg++] = vertex[i]; + vpos[npos++] = vertex[i]; + } + if((isPos[i] && isNeg[ip]) || (isNeg[i] && isPos[ip])) { + Vector vi = bsp->IntersectionWith(vertex[i], vertex[ip]); + vpos[npos++] = vi; + vneg[nneg++] = vi; + + if(inters >= 2) return false; // triangulate: XXX shouldn't happen but does + inter[inters++] = vi; + } } - if(isNeg[i]) { - vneg[nneg++] = vertex[i]; + ssassert(npos <= cnt + 1 && nneg <= cnt + 1, "Impossible"); + + if(insertEdges) { + Vector e01 = (vertex[1]).Minus(vertex[0]); + Vector e12 = (vertex[2]).Minus(vertex[1]); + Vector out = e01.Cross(e12); + if(inters == 2) { + SEdge se = SEdge::From(inter[0], inter[1]); + bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, out); + } else if(inters == 1 && onc == 1) { + SEdge se = SEdge::From(inter[0], on[0]); + bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, out); + } else if(inters == 0 && onc == 2) { + // We already handled this on-plane existing edge + } else { + return false; //triangulate; + } } - if(isOn[i]) { - vneg[nneg++] = vertex[i]; - vpos[npos++] = vertex[i]; + if(nneg < 3 || npos < 3) return false; // triangulate; // XXX + + return true; + } + + void ProcessEdgeInsert() { + ssassert(onc == 2, "Impossible"); + + Vector a, b; + if (!isOn[0]) { a = tr->b; b = tr->c; } + else if(!isOn[1]) { a = tr->c; b = tr->a; } + else if(!isOn[2]) { a = tr->a; b = tr->b; } + else ssassert(false, "Impossible"); + + SEdge se = SEdge::From(a, b); + bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, tr->Normal()); + } + + bool SplitIntoTwoTriangles(bool insertEdge) { + ssassert(posc == 1 && negc == 1 && onc == 1, "Impossible"); + + bool bpos; + Vector a, b, c; + + // Standardize so that a is on the plane + if (isOn[0]) { a = tr->a; b = tr->b; c = tr->c; bpos = isPos[1]; + } else if(isOn[1]) { a = tr->b; b = tr->c; c = tr->a; bpos = isPos[2]; + } else if(isOn[2]) { a = tr->c; b = tr->a; c = tr->b; bpos = isPos[0]; + } else ssassert(false, "Impossible"); + + AllocTriangles(); + Vector bPc = bsp->IntersectionWith(b, c); + *btri = STriangle::From(tr->meta, a, b, bPc); + *ctri = STriangle::From(tr->meta, c, a, bPc); + + if(insertEdge) { + SEdge se = SEdge::From(a, bPc); + bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, tr->Normal()); } - if((isPos[i] && isNeg[ip]) || (isNeg[i] && isPos[ip])) { - Vector vi = IntersectionWith(vertex[i], vertex[ip]); - vpos[npos++] = vi; - vneg[nneg++] = vi; - if(inters >= 2) goto triangulate; // XXX shouldn't happen but does - inter[inters++] = vi; + return bpos; + } + + bool SplitIntoTwoPieces(bool insertEdge) { + Vector a, b, c; + if(posc == 2 && negc == 1) { + // Standardize so that a is on one side, and b and c are on the other. + if (isNeg[0]) { a = tr->a; b = tr->b; c = tr->c; + } else if(isNeg[1]) { a = tr->b; b = tr->c; c = tr->a; + } else if(isNeg[2]) { a = tr->c; b = tr->a; c = tr->b; + } else ssassert(false, "Impossible"); + } else if(posc == 1 && negc == 2) { + if (isPos[0]) { a = tr->a; b = tr->b; c = tr->c; + } else if(isPos[1]) { a = tr->b; b = tr->c; c = tr->a; + } else if(isPos[2]) { a = tr->c; b = tr->a; c = tr->b; + } else ssassert(false, "Impossible"); + } else ssassert(false, "Impossible"); + + Vector aPb = bsp->IntersectionWith(a, b); + Vector cPa = bsp->IntersectionWith(c, a); + AllocTriangle(); + AllocQuad(); + + *btri = STriangle::From(tr->meta, a, aPb, cPa); + + vpos[0] = aPb; + vpos[1] = b; + vpos[2] = c; + vpos[3] = cPa; + + if(insertEdge) { + SEdge se = SEdge::From(aPb, cPa); + bsp->edges = SBsp2::InsertOrCreateEdge(bsp->edges, &se, bsp->n, btri->Normal()); } + + return posc == 2 && negc == 1; } - if(npos > cnt + 1 || nneg > cnt + 1) oops(); - - if(!instead) { - if(inters == 2) { - SEdge se = SEdge::From(inter[0], inter[1]); - edges = SBsp2::InsertOrCreateEdge(edges, &se, n, out); - } else if(inters == 1 && onc == 1) { - SEdge se = SEdge::From(inter[0], on[0]); - edges = SBsp2::InsertOrCreateEdge(edges, &se, n, out); - } else if(inters == 0 && onc == 2) { - // We already handled this on-plane existing edge - } else { - goto triangulate; + + static SBsp3 *Triangulate(SBsp3 *bsp, const STriMeta &meta, Vector *vertex, + size_t cnt, SMesh *instead) { + for(size_t i = 0; i < cnt - 2; i++) { + STriangle tr = STriangle::From(meta, vertex[0], vertex[i + 1], vertex[i + 2]); + bsp = SBsp3::InsertOrCreate(bsp, &tr, instead); } + return bsp; } - if(nneg < 3 || npos < 3) goto triangulate; // XXX +}; - InsertConvexHow(NEG, meta, vneg, nneg, instead); - InsertConvexHow(POS, meta, vpos, npos, instead); - return this; +void SBsp3::InsertConvexHow(BspClass how, STriMeta meta, Vector *vertex, size_t n, + SMesh *instead) { + switch(how) { + case BspClass::POS: + if(pos) { + pos = pos->InsertConvex(meta, vertex, n, instead); + return; + } + break; -triangulate: - // We don't handle the special case for this; do it as triangles - SBsp3 *r = this; - for(i = 0; i < cnt - 2; i++) { + case BspClass::NEG: + if(neg) { + neg = neg->InsertConvex(meta, vertex, n, instead); + return; + } + break; + + default: ssassert(false, "Unexpected BSP insert type"); + } + + for(size_t i = 0; i < n - 2; i++) { STriangle tr = STriangle::From(meta, vertex[0], vertex[i+1], vertex[i+2]); - r = InsertOrCreate(r, &tr, instead); + InsertHow(how, &tr, instead); + } +} + +SBsp3 *SBsp3::InsertConvex(STriMeta meta, Vector *vertex, size_t cnt, SMesh *instead) { + BspUtil *u = BspUtil::Alloc(); + if(u->ClassifyConvex(vertex, cnt, this, !instead)) { + if(u->posc == 0) { + InsertConvexHow(BspClass::NEG, meta, vertex, cnt, instead); + return this; + } + if(u->negc == 0) { + InsertConvexHow(BspClass::POS, meta, vertex, cnt, instead); + return this; + } + + if(u->ClassifyConvexVertices(vertex, cnt, !instead)) { + InsertConvexHow(BspClass::NEG, meta, u->vneg, u->nneg, instead); + InsertConvexHow(BspClass::POS, meta, u->vpos, u->npos, instead); + return this; + } } - return r; + + // We don't handle the special case for this; do it as triangles + return BspUtil::Triangulate(this, meta, vertex, cnt, instead); } SBsp3 *SBsp3::InsertOrCreate(SBsp3 *where, STriangle *tr, SMesh *instead) { if(where == NULL) { if(instead) { + // ruevs: I do not think this code is reachable, but in + // principle should we use instead->keepInsideOtherShell + // in place of instead->flipNormal ? if(instead->flipNormal) { instead->atLeastOneDiscarded = true; } else { @@ -289,120 +462,52 @@ SBsp3 *SBsp3::InsertOrCreate(SBsp3 *where, STriangle *tr, SMesh *instead) { } void SBsp3::Insert(STriangle *tr, SMesh *instead) { - double dt[3] = { (tr->a).Dot(n), (tr->b).Dot(n), (tr->c).Dot(n) }; - - int inc = 0, posc = 0, negc = 0; - bool isPos[3] = {}, isNeg[3] = {}, isOn[3] = {}; - // Count vertices in the plane - for(int i = 0; i < 3; i++) { - if(fabs(dt[i] - d) < LENGTH_EPS) { - inc++; - isOn[i] = true; - } else if(dt[i] > d) { - posc++; - isPos[i] = true; - } else { - negc++; - isNeg[i] = true; - } - } + BspUtil *u = BspUtil::Alloc(); + u->ClassifyTriangle(tr, this); // All vertices in-plane - if(inc == 3) { - InsertHow(COPLANAR, tr, instead); + if(u->onc == 3) { + InsertHow(BspClass::COPLANAR, tr, instead); return; } // No split required - if(posc == 0 || negc == 0) { - if(inc == 2) { - Vector a, b; - if (!isOn[0]) { a = tr->b; b = tr->c; } - else if(!isOn[1]) { a = tr->c; b = tr->a; } - else if(!isOn[2]) { a = tr->a; b = tr->b; } - else oops(); - if(!instead) { - SEdge se = SEdge::From(a, b); - edges = SBsp2::InsertOrCreateEdge(edges, &se, n, tr->Normal()); - } + if(u->posc == 0 || u->negc == 0) { + if(!instead && u->onc == 2) { + u->ProcessEdgeInsert(); } - if(posc > 0) { - InsertHow(POS, tr, instead); + if(u->posc > 0) { + InsertHow(BspClass::POS, tr, instead); } else { - InsertHow(NEG, tr, instead); + InsertHow(BspClass::NEG, tr, instead); } return; } - // The polygon must be split into two pieces, one above, one below. - Vector a, b, c; - - if(posc == 1 && negc == 1 && inc == 1) { - bool bpos; - // Standardize so that a is on the plane - if (isOn[0]) { a = tr->a; b = tr->b; c = tr->c; bpos = isPos[1]; - } else if(isOn[1]) { a = tr->b; b = tr->c; c = tr->a; bpos = isPos[2]; - } else if(isOn[2]) { a = tr->c; b = tr->a; c = tr->b; bpos = isPos[0]; - } else oops(); - - Vector bPc = IntersectionWith(b, c); - STriangle btri = STriangle::From(tr->meta, a, b, bPc); - STriangle ctri = STriangle::From(tr->meta, c, a, bPc); - - if(bpos) { - InsertHow(POS, &btri, instead); - InsertHow(NEG, &ctri, instead); + // The polygon must be split into two triangles, one above, one below. + if(u->posc == 1 && u->negc == 1 && u->onc == 1) { + if(u->SplitIntoTwoTriangles(!instead)) { + InsertHow(BspClass::POS, u->btri, instead); + InsertHow(BspClass::NEG, u->ctri, instead); } else { - InsertHow(POS, &ctri, instead); - InsertHow(NEG, &btri, instead); - } - - if(!instead) { - SEdge se = SEdge::From(a, bPc); - edges = SBsp2::InsertOrCreateEdge(edges, &se, n, tr->Normal()); + InsertHow(BspClass::POS, u->ctri, instead); + InsertHow(BspClass::NEG, u->btri, instead); } - return; } - if(posc == 2 && negc == 1) { - // Standardize so that a is on one side, and b and c are on the other. - if (isNeg[0]) { a = tr->a; b = tr->b; c = tr->c; - } else if(isNeg[1]) { a = tr->b; b = tr->c; c = tr->a; - } else if(isNeg[2]) { a = tr->c; b = tr->a; c = tr->b; - } else oops(); - - } else if(posc == 1 && negc == 2) { - if (isPos[0]) { a = tr->a; b = tr->b; c = tr->c; - } else if(isPos[1]) { a = tr->b; b = tr->c; c = tr->a; - } else if(isPos[2]) { a = tr->c; b = tr->a; c = tr->b; - } else oops(); - } else oops(); - - Vector aPb = IntersectionWith(a, b); - Vector cPa = IntersectionWith(c, a); - - STriangle alone = STriangle::From(tr->meta, a, aPb, cPa); - Vector quad[4] = { aPb, b, c, cPa }; - - if(posc == 2 && negc == 1) { - InsertConvexHow(POS, tr->meta, quad, 4, instead); - InsertHow(NEG, &alone, instead); + // The polygon must be split into two pieces: a triangle and a quad. + if(u->SplitIntoTwoPieces(!instead)) { + InsertConvexHow(BspClass::POS, tr->meta, u->vpos, 4, instead); + InsertHow(BspClass::NEG, u->btri, instead); } else { - InsertConvexHow(NEG, tr->meta, quad, 4, instead); - InsertHow(POS, &alone, instead); - } - if(!instead) { - SEdge se = SEdge::From(aPb, cPa); - edges = SBsp2::InsertOrCreateEdge(edges, &se, n, alone.Normal()); + InsertConvexHow(BspClass::NEG, tr->meta, u->vpos, 4, instead); + InsertHow(BspClass::POS, u->btri, instead); } - - return; } -void SBsp3::GenerateInPaintOrder(SMesh *m) { - +void SBsp3::GenerateInPaintOrder(SMesh *m) const { // Doesn't matter which branch we take if the normal has zero z // component, so don't need a separate case for that. if(n.z < 0) { @@ -411,7 +516,7 @@ void SBsp3::GenerateInPaintOrder(SMesh *m) { if(neg) neg->GenerateInPaintOrder(m); } - SBsp3 *flip = this; + const SBsp3 *flip = this; while(flip) { m->AddTriangle(&(flip->tri)); flip = flip->more; @@ -424,54 +529,12 @@ void SBsp3::GenerateInPaintOrder(SMesh *m) { } } -void SBsp3::DebugDraw(void) { - - if(pos) pos->DebugDraw(); - Vector norm = tri.Normal(); - glNormal3d(norm.x, norm.y, norm.z); - - glEnable(GL_DEPTH_TEST); - glEnable(GL_LIGHTING); - glBegin(GL_TRIANGLES); - ssglVertex3v(tri.a); - ssglVertex3v(tri.b); - ssglVertex3v(tri.c); - glEnd(); - - glDisable(GL_LIGHTING); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - ssglDepthRangeOffset(2); - glBegin(GL_TRIANGLES); - ssglVertex3v(tri.a); - ssglVertex3v(tri.b); - ssglVertex3v(tri.c); - glEnd(); - - glDisable(GL_LIGHTING); - glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); - glPointSize(10); - ssglDepthRangeOffset(2); - glBegin(GL_TRIANGLES); - ssglVertex3v(tri.a); - ssglVertex3v(tri.b); - ssglVertex3v(tri.c); - glEnd(); - - ssglDepthRangeOffset(0); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - - if(more) more->DebugDraw(); - if(neg) neg->DebugDraw(); - - if(edges) edges->DebugDraw(n, d); -} - ///////////////////////////////// -Vector SBsp2::IntersectionWith(Vector a, Vector b) { +Vector SBsp2::IntersectionWith(Vector a, Vector b) const { double da = a.Dot(no) - d; double db = b.Dot(no) - d; - if(da*db > 0) oops(); + ssassert(da*db < 0, "Expected segment to intersect BSP node"); double dab = (db - da); return (a.ScaledBy(db/dab)).Plus(b.ScaledBy(-da/dab)); @@ -547,28 +610,28 @@ void SBsp2::InsertEdge(SEdge *nedge, Vector nnp, Vector out) { } return; } - oops(); + ssassert(false, "Impossible"); } -void SBsp2::InsertTriangleHow(int how, STriangle *tr, SMesh *m, SBsp3 *bsp3) { +void SBsp2::InsertTriangleHow(BspClass how, STriangle *tr, SMesh *m, SBsp3 *bsp3) { switch(how) { - case POS: + case BspClass::POS: if(pos) { pos->InsertTriangle(tr, m, bsp3); } else { - bsp3->InsertInPlane(true, tr, m); + bsp3->InsertInPlane(/*pos2=*/true, tr, m); } break; - case NEG: + case BspClass::NEG: if(neg) { neg->InsertTriangle(tr, m, bsp3); } else { - bsp3->InsertInPlane(false, tr, m); + bsp3->InsertInPlane(/*pos2=*/false, tr, m); } break; - default: oops(); + default: ssassert(false, "Unexpected BSP insert type"); } } @@ -598,9 +661,9 @@ void SBsp2::InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3) { // No split required if(posc == 0 || negc == 0) { if(posc > 0) { - InsertTriangleHow(POS, tr, m, bsp3); + InsertTriangleHow(BspClass::POS, tr, m, bsp3); } else { - InsertTriangleHow(NEG, tr, m, bsp3); + InsertTriangleHow(BspClass::NEG, tr, m, bsp3); } return; } @@ -614,18 +677,18 @@ void SBsp2::InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3) { if (isOn[0]) { a = tr->a; b = tr->b; c = tr->c; bpos = isPos[1]; } else if(isOn[1]) { a = tr->b; b = tr->c; c = tr->a; bpos = isPos[2]; } else if(isOn[2]) { a = tr->c; b = tr->a; c = tr->b; bpos = isPos[0]; - } else oops(); + } else ssassert(false, "Impossible"); Vector bPc = IntersectionWith(b, c); STriangle btri = STriangle::From(tr->meta, a, b, bPc); STriangle ctri = STriangle::From(tr->meta, c, a, bPc); if(bpos) { - InsertTriangleHow(POS, &btri, m, bsp3); - InsertTriangleHow(NEG, &ctri, m, bsp3); + InsertTriangleHow(BspClass::POS, &btri, m, bsp3); + InsertTriangleHow(BspClass::NEG, &ctri, m, bsp3); } else { - InsertTriangleHow(POS, &ctri, m, bsp3); - InsertTriangleHow(NEG, &btri, m, bsp3); + InsertTriangleHow(BspClass::POS, &ctri, m, bsp3); + InsertTriangleHow(BspClass::NEG, &btri, m, bsp3); } return; @@ -636,14 +699,14 @@ void SBsp2::InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3) { if (isNeg[0]) { a = tr->a; b = tr->b; c = tr->c; } else if(isNeg[1]) { a = tr->b; b = tr->c; c = tr->a; } else if(isNeg[2]) { a = tr->c; b = tr->a; c = tr->b; - } else oops(); + } else ssassert(false, "Impossible"); } else if(posc == 1 && negc == 2) { if (isPos[0]) { a = tr->a; b = tr->b; c = tr->c; } else if(isPos[1]) { a = tr->b; b = tr->c; c = tr->a; } else if(isPos[2]) { a = tr->c; b = tr->a; c = tr->b; - } else oops(); - } else oops(); + } else ssassert(false, "Impossible"); + } else ssassert(false, "Impossible"); Vector aPb = IntersectionWith(a, b); Vector cPa = IntersectionWith(c, a); @@ -653,30 +716,14 @@ void SBsp2::InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3) { STriangle quad2 = STriangle::From(tr->meta, aPb, c, cPa); if(posc == 2 && negc == 1) { - InsertTriangleHow(POS, &quad1, m, bsp3); - InsertTriangleHow(POS, &quad2, m, bsp3); - InsertTriangleHow(NEG, &alone, m, bsp3); + InsertTriangleHow(BspClass::POS, &quad1, m, bsp3); + InsertTriangleHow(BspClass::POS, &quad2, m, bsp3); + InsertTriangleHow(BspClass::NEG, &alone, m, bsp3); } else { - InsertTriangleHow(NEG, &quad1, m, bsp3); - InsertTriangleHow(NEG, &quad2, m, bsp3); - InsertTriangleHow(POS, &alone, m, bsp3); + InsertTriangleHow(BspClass::NEG, &quad1, m, bsp3); + InsertTriangleHow(BspClass::NEG, &quad2, m, bsp3); + InsertTriangleHow(BspClass::POS, &alone, m, bsp3); } return; } - -void SBsp2::DebugDraw(Vector n, double d) { - if(fabs((edge.a).Dot(n) - d) > LENGTH_EPS) oops(); - if(fabs((edge.b).Dot(n) - d) > LENGTH_EPS) oops(); - - ssglLineWidth(10); - glBegin(GL_LINES); - ssglVertex3v(edge.a); - ssglVertex3v(edge.b); - glEnd(); - if(pos) pos->DebugDraw(n, d); - if(neg) neg->DebugDraw(n, d); - if(more) more->DebugDraw(n, d); - ssglLineWidth(1); -} - diff --git a/src/clipboard.cpp b/src/clipboard.cpp index c13b4fb..011b658 100644 --- a/src/clipboard.cpp +++ b/src/clipboard.cpp @@ -6,22 +6,22 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -void SolveSpaceUI::Clipboard::Clear(void) { +void SolveSpaceUI::Clipboard::Clear() { c.Clear(); r.Clear(); } bool SolveSpaceUI::Clipboard::ContainsEntity(hEntity he) { - if(he.v == Entity::NO_ENTITY.v) + if(he == Entity::NO_ENTITY) return true; ClipboardRequest *cr; for(cr = r.First(); cr; cr = r.NextAfter(cr)) { - if(cr->oldEnt.v == he.v) + if(cr->oldEnt == he) return true; for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) { - if(cr->oldPointEnt[i].v == he.v) + if(cr->oldPointEnt[i] == he) return true; } } @@ -29,23 +29,24 @@ bool SolveSpaceUI::Clipboard::ContainsEntity(hEntity he) { } hEntity SolveSpaceUI::Clipboard::NewEntityFor(hEntity he) { - if(he.v == Entity::NO_ENTITY.v) + if(he == Entity::NO_ENTITY) return Entity::NO_ENTITY; ClipboardRequest *cr; for(cr = r.First(); cr; cr = r.NextAfter(cr)) { - if(cr->oldEnt.v == he.v) + if(cr->oldEnt == he) return cr->newReq.entity(0); for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) { - if(cr->oldPointEnt[i].v == he.v) + if(cr->oldPointEnt[i] == he) return cr->newReq.entity(1+i); } } - oops(); + + ssassert(false, "Expected to find entity in some clipboard request"); } -void GraphicsWindow::DeleteSelection(void) { +void GraphicsWindow::DeleteSelection() { SK.request.ClearTags(); SK.constraint.ClearTags(); List *ls = &(selection); @@ -68,7 +69,7 @@ void GraphicsWindow::DeleteSelection(void) { DeleteTaggedRequests(); } -void GraphicsWindow::CopySelection(void) { +void GraphicsWindow::CopySelection() { SS.clipboard.Clear(); Entity *wrkpl = SK.GetEntity(ActiveWorkplane()); @@ -84,13 +85,18 @@ void GraphicsWindow::CopySelection(void) { // Work only on entities that have requests that will generate them. Entity *e = SK.GetEntity(s->entity); bool hasDistance; - int req, pts; + Request::Type req; + int pts; if(!EntReqTable::GetEntityInfo(e->type, e->extraPoints, &req, &pts, NULL, &hasDistance)) { - continue; + if(!e->h.isFromRequest()) continue; + Request *r = SK.GetRequest(e->h.request()); + if(r->type != Request::Type::DATUM_POINT) continue; + EntReqTable::GetEntityInfo((Entity::Type)0, e->extraPoints, + &req, &pts, NULL, &hasDistance); } - if(req == Request::WORKPLANE) continue; + if(req == Request::Type::WORKPLANE) continue; ClipboardRequest cr = {}; cr.type = req; @@ -98,9 +104,15 @@ void GraphicsWindow::CopySelection(void) { cr.style = e->style; cr.str = e->str; cr.font = e->font; + cr.file = e->file; cr.construction = e->construction; {for(int i = 0; i < pts; i++) { - Vector pt = SK.GetEntity(e->point[i])->PointGetNum(); + Vector pt; + if(req == Request::Type::DATUM_POINT) { + pt = e->PointGetNum(); + } else { + pt = SK.GetEntity(e->point[i])->PointGetNum(); + } pt = pt.Minus(p); pt = pt.DotInToCsys(u, v, n); cr.point[i] = pt; @@ -121,7 +133,7 @@ void GraphicsWindow::CopySelection(void) { if(!s->constraint.v) continue; Constraint *c = SK.GetConstraint(s->constraint); - if(c->type == Constraint::COMMENT) { + if(c->type == Constraint::Type::COMMENT) { SS.clipboard.c.Add(c); } } @@ -134,7 +146,7 @@ void GraphicsWindow::CopySelection(void) { !SS.clipboard.ContainsEntity(c->entityB) || !SS.clipboard.ContainsEntity(c->entityC) || !SS.clipboard.ContainsEntity(c->entityD) || - c->type == Constraint::COMMENT) { + c->type == Constraint::Type::COMMENT) { continue; } SS.clipboard.c.Add(c); @@ -149,18 +161,38 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { n = wrkpln->NormalN(), p = SK.GetEntity(wrkpl->point[0])->PointGetNum(); + // For arcs, reflection involves swapping the endpoints, or otherwise + // the arc gets inverted. + auto mapPoint = [scale](hEntity he) { + if(he.v == 0) return he; + + if(scale < 0) { + hRequest hr = he.request(); + Request *r = SK.GetRequest(hr); + if(r->type == Request::Type::ARC_OF_CIRCLE) { + if(he == hr.entity(2)) { + return hr.entity(3); + } else if(he == hr.entity(3)) { + return hr.entity(2); + } + } + } + return he; + }; + ClipboardRequest *cr; for(cr = SS.clipboard.r.First(); cr; cr = SS.clipboard.r.NextAfter(cr)) { - hRequest hr = AddRequest(cr->type, false); + hRequest hr = AddRequest(cr->type, /*rememberForUndo=*/false); Request *r = SK.GetRequest(hr); r->extraPoints = cr->extraPoints; r->style = cr->style; r->str = cr->str; r->font = cr->font; + r->file = cr->file; r->construction = cr->construction; // Need to regen to get the right number of points, if extraPoints // changed. - SS.GenerateAll(SolveSpaceUI::GENERATE_REGEN); + SS.GenerateAll(SolveSpaceUI::Generate::REGEN); SS.MarkGroupDirty(r->group); bool hasDistance; int i, pts; @@ -180,7 +212,8 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { pt = pt.Plus(p); pt = pt.RotatedAbout(n, theta); pt = pt.Plus(trans); - SK.GetEntity(hr.entity(i+1))->PointForceTo(pt); + int j = (r->type == Request::Type::DATUM_POINT) ? i : i + 1; + SK.GetEntity(mapPoint(hr.entity(j)))->PointForceTo(pt); } if(hasDistance) { SK.GetEntity(hr.entity(64))->DistanceForceTo( @@ -190,7 +223,8 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { cr->newReq = hr; MakeSelected(hr.entity(0)); for(i = 0; i < pts; i++) { - MakeSelected(hr.entity(i+1)); + int j = (r->type == Request::Type::DATUM_POINT) ? i : i + 1; + MakeSelected(hr.entity(j)); } } @@ -201,8 +235,8 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { c.workplane = SS.GW.ActiveWorkplane(); c.type = cc->type; c.valA = cc->valA; - c.ptA = SS.clipboard.NewEntityFor(cc->ptA); - c.ptB = SS.clipboard.NewEntityFor(cc->ptB); + c.ptA = SS.clipboard.NewEntityFor(mapPoint(cc->ptA)); + c.ptB = SS.clipboard.NewEntityFor(mapPoint(cc->ptB)); c.entityA = SS.clipboard.NewEntityFor(cc->entityA); c.entityB = SS.clipboard.NewEntityFor(cc->entityB); c.entityC = SS.clipboard.NewEntityFor(cc->entityC); @@ -213,14 +247,14 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { c.disp = cc->disp; c.comment = cc->comment; switch(c.type) { - case Constraint::COMMENT: + case Constraint::Type::COMMENT: c.disp.offset = c.disp.offset.Plus(trans); break; - case Constraint::PT_PT_DISTANCE: - case Constraint::PT_LINE_DISTANCE: - case Constraint::PROJ_PT_DISTANCE: - case Constraint::DIAMETER: + case Constraint::Type::PT_PT_DISTANCE: + case Constraint::Type::PT_LINE_DISTANCE: + case Constraint::Type::PROJ_PT_DISTANCE: + case Constraint::Type::DIAMETER: c.valA *= fabs(scale); break; @@ -229,23 +263,21 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { } hConstraint hc = Constraint::AddConstraint(&c, /*rememberForUndo=*/false); - if(c.type == Constraint::COMMENT) { + if(c.type == Constraint::Type::COMMENT) { MakeSelected(hc); } } - - SS.ScheduleGenerateAll(); } -void GraphicsWindow::MenuClipboard(int id) { - if(id != MNU_DELETE && !SS.GW.LockedInWorkplane()) { - Error("Cut, paste, and copy work only in a workplane.\n\n" - "Select one with Sketch -> In Workplane."); +void GraphicsWindow::MenuClipboard(Command id) { + if(id != Command::DELETE && !SS.GW.LockedInWorkplane()) { + Error(_("Cut, paste, and copy work only in a workplane.\n\n" + "Activate one with Sketch -> In Workplane.")); return; } switch(id) { - case MNU_PASTE: { + case Command::PASTE: { SS.UndoRemember(); Vector trans = SS.GW.projRight.ScaledBy(80/SS.GW.scale).Plus( SS.GW.projUp .ScaledBy(40/SS.GW.scale)); @@ -254,9 +286,9 @@ void GraphicsWindow::MenuClipboard(int id) { break; } - case MNU_PASTE_TRANSFORM: { - if(SS.clipboard.r.n == 0) { - Error("Clipboard is empty; nothing to paste."); + case Command::PASTE_TRANSFORM: { + if(SS.clipboard.r.IsEmpty()) { + Error(_("Clipboard is empty; nothing to paste.")); break; } @@ -267,59 +299,59 @@ void GraphicsWindow::MenuClipboard(int id) { SS.TW.shown.paste.theta = 0; SS.TW.shown.paste.origin = p; SS.TW.shown.paste.scale = 1; - SS.TW.GoToScreen(TextWindow::SCREEN_PASTE_TRANSFORMED); + SS.TW.GoToScreen(TextWindow::Screen::PASTE_TRANSFORMED); SS.GW.ForceTextWindowShown(); SS.ScheduleShowTW(); break; } - case MNU_COPY: + case Command::COPY: SS.GW.CopySelection(); SS.GW.ClearSelection(); break; - case MNU_CUT: + case Command::CUT: SS.UndoRemember(); SS.GW.CopySelection(); SS.GW.DeleteSelection(); break; - case MNU_DELETE: + case Command::DELETE: SS.UndoRemember(); SS.GW.DeleteSelection(); break; - default: oops(); + default: ssassert(false, "Unexpected menu ID"); } } -bool TextWindow::EditControlDoneForPaste(const char *s) { +bool TextWindow::EditControlDoneForPaste(const std::string &s) { Expr *e; switch(edit.meaning) { - case EDIT_PASTE_TIMES_REPEATED: { - e = Expr::From(s, true); + case Edit::PASTE_TIMES_REPEATED: { + e = Expr::From(s, /*popUpError=*/true); if(!e) break; int v = (int)e->Eval(); if(v > 0) { shown.paste.times = v; } else { - Error("Number of copies to paste must be at least one."); + Error(_("Number of copies to paste must be at least one.")); } break; } - case EDIT_PASTE_ANGLE: - e = Expr::From(s, true); + case Edit::PASTE_ANGLE: + e = Expr::From(s, /*popUpError=*/true); if(!e) break; shown.paste.theta = WRAP_SYMMETRIC((e->Eval())*PI/180, 2*PI); break; - case EDIT_PASTE_SCALE: { - e = Expr::From(s, true); + case Edit::PASTE_SCALE: { + e = Expr::From(s, /*popUpError=*/true); double v = e->Eval(); if(fabs(v) > 1e-6) { - shown.paste.scale = v; + shown.paste.scale = shown.paste.scale < 0 ? -v : v; } else { - Error("Scale cannot be zero."); + Error(_("Scale cannot be zero.")); } break; } @@ -334,17 +366,21 @@ void TextWindow::ScreenChangePasteTransformed(int link, uint32_t v) { switch(link) { case 't': SS.TW.ShowEditControl(13, ssprintf("%d", SS.TW.shown.paste.times)); - SS.TW.edit.meaning = EDIT_PASTE_TIMES_REPEATED; + SS.TW.edit.meaning = Edit::PASTE_TIMES_REPEATED; break; case 'r': SS.TW.ShowEditControl(13, ssprintf("%.3f", SS.TW.shown.paste.theta*180/PI)); - SS.TW.edit.meaning = EDIT_PASTE_ANGLE; + SS.TW.edit.meaning = Edit::PASTE_ANGLE; break; case 's': - SS.TW.ShowEditControl(13, ssprintf("%.3f", SS.TW.shown.paste.scale)); - SS.TW.edit.meaning = EDIT_PASTE_SCALE; + SS.TW.ShowEditControl(13, ssprintf("%.3f", fabs(SS.TW.shown.paste.scale))); + SS.TW.edit.meaning = Edit::PASTE_SCALE; + break; + + case 'f': + SS.TW.shown.paste.scale *= -1; break; } } @@ -357,7 +393,7 @@ void TextWindow::ScreenPasteTransformed(int link, uint32_t v) { Entity *e = SK.GetEntity(SS.GW.gs.point[0]); SS.TW.shown.paste.origin = e->PointGetNum(); } else { - Error("Select one point to define origin of rotation."); + Error(_("Select one point to define origin of rotation.")); } SS.GW.ClearSelection(); break; @@ -369,7 +405,7 @@ void TextWindow::ScreenPasteTransformed(int link, uint32_t v) { SS.TW.shown.paste.trans = (pb->PointGetNum()).Minus(pa->PointGetNum()); } else { - Error("Select two points to define translation vector."); + Error(_("Select two points to define translation vector.")); } SS.GW.ClearSelection(); break; @@ -379,16 +415,16 @@ void TextWindow::ScreenPasteTransformed(int link, uint32_t v) { SS.TW.shown.paste.trans.Magnitude() < LENGTH_EPS && SS.TW.shown.paste.times != 1) { - Message("Transformation is identity. So all copies will be " - "exactly on top of each other."); + Message(_("Transformation is identity. So all copies will be " + "exactly on top of each other.")); } if(SS.TW.shown.paste.times*SS.clipboard.r.n > 100) { - Error("Too many items to paste; split this into smaller " - "pastes."); + Error(_("Too many items to paste; split this into smaller " + "pastes.")); break; } if(!SS.GW.LockedInWorkplane()) { - Error("No workplane active."); + Error(_("No workplane active.")); break; } Entity *wrkpl = SK.GetEntity(SS.GW.ActiveWorkplane()); @@ -408,14 +444,14 @@ void TextWindow::ScreenPasteTransformed(int link, uint32_t v) { SS.GW.PasteClipboard(t, theta, SS.TW.shown.paste.scale); } - SS.TW.GoToScreen(SCREEN_LIST_OF_GROUPS); + SS.TW.GoToScreen(Screen::LIST_OF_GROUPS); SS.ScheduleShowTW(); break; } } } -void TextWindow::ShowPasteTransformed(void) { +void TextWindow::ShowPasteTransformed() { Printf(true, "%FtPASTE TRANSFORMED%E"); Printf(true, "%Ba %Ftrepeat%E %d time%s %Fl%Lt%f[change]%E", shown.paste.times, (shown.paste.times == 1) ? "" : "s", @@ -434,8 +470,11 @@ void TextWindow::ShowPasteTransformed(void) { SS.MmToString(shown.paste.trans.z).c_str(), &ScreenPasteTransformed); Printf(false, "%Ba %Ftscale%E %@ %Fl%Ls%f[change]%E", - shown.paste.scale, + fabs(shown.paste.scale), &ScreenChangePasteTransformed); + Printf(false, "%Ba %Ftmirror%E %Fd%Lf%f%s flip%E", + &ScreenChangePasteTransformed, + shown.paste.scale < 0 ? CHECK_TRUE : CHECK_FALSE); Printf(true, " %Fl%Lg%fpaste transformed now%E", &ScreenPasteTransformed); diff --git a/src/cocoa/cocoamain.mm b/src/cocoa/cocoamain.mm deleted file mode 100644 index 1b6d3e6..0000000 --- a/src/cocoa/cocoamain.mm +++ /dev/null @@ -1,1351 +0,0 @@ -//----------------------------------------------------------------------------- -// Our main() function, and Cocoa-specific stuff to set up our windows and -// otherwise handle our interface to the operating system. Everything -// outside gtk/... should be standard C++ and OpenGL. -// -// Copyright 2015 -//----------------------------------------------------------------------------- -#include -#include - -#import - -#include -#include - -#include "solvespace.h" -#include "../unix/gloffscreen.h" -#include - -using SolveSpace::dbp; - -#define GL_CHECK() \ - do { \ - int err = (int)glGetError(); \ - if(err) dbp("%s:%d: glGetError() == 0x%X", __FILE__, __LINE__, err); \ - } while (0) - -/* Settings */ - -namespace SolveSpace { -void CnfFreezeInt(uint32_t val, const std::string &key) { - [[NSUserDefaults standardUserDefaults] - setInteger:val forKey:[NSString stringWithUTF8String:key.c_str()]]; -} - -uint32_t CnfThawInt(uint32_t val, const std::string &key) { - NSString *nsKey = [NSString stringWithUTF8String:key.c_str()]; - if([[NSUserDefaults standardUserDefaults] objectForKey:nsKey]) - return [[NSUserDefaults standardUserDefaults] integerForKey:nsKey]; - return val; -} - -void CnfFreezeFloat(float val, const std::string &key) { - [[NSUserDefaults standardUserDefaults] - setFloat:val forKey:[NSString stringWithUTF8String:key.c_str()]]; -} - -float CnfThawFloat(float val, const std::string &key) { - NSString *nsKey = [NSString stringWithUTF8String:key.c_str()]; - if([[NSUserDefaults standardUserDefaults] objectForKey:nsKey]) - return [[NSUserDefaults standardUserDefaults] floatForKey:nsKey]; - return val; -} - -void CnfFreezeString(const std::string &val, const std::string &key) { - [[NSUserDefaults standardUserDefaults] - setObject:[NSString stringWithUTF8String:val.c_str()] - forKey:[NSString stringWithUTF8String:key.c_str()]]; -} - -std::string CnfThawString(const std::string &val, const std::string &key) { - NSString *nsKey = [NSString stringWithUTF8String:key.c_str()]; - if([[NSUserDefaults standardUserDefaults] objectForKey:nsKey]) { - NSString *nsNewVal = [[NSUserDefaults standardUserDefaults] stringForKey:nsKey]; - return [nsNewVal UTF8String]; - } - return val; -} -}; - -/* Timer */ - -int64_t SolveSpace::GetMilliseconds(void) { - clock_serv_t cclock; - mach_timespec_t mts; - - host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - - return mts.tv_sec * 1000 + mts.tv_nsec / 1000000; -} - -@interface DeferredHandler : NSObject -+ (void) runLater:(id)dummy; -+ (void) runCallback; -+ (void) doAutosave; -@end - -@implementation DeferredHandler -+ (void) runLater:(id)dummy { - SolveSpace::SS.DoLater(); -} -+ (void) runCallback { - SolveSpace::SS.GW.TimerCallback(); - SolveSpace::SS.TW.TimerCallback(); -} -+ (void) doAutosave { - SolveSpace::SS.Autosave(); -} -@end - -static void Schedule(SEL selector, double interval) { - NSMethodSignature *signature = [[DeferredHandler class] - methodSignatureForSelector:selector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; - [invocation setSelector:selector]; - [invocation setTarget:[DeferredHandler class]]; - [NSTimer scheduledTimerWithTimeInterval:interval - invocation:invocation repeats:NO]; -} - -void SolveSpace::SetTimerFor(int milliseconds) { - Schedule(@selector(runCallback), milliseconds / 1000.0); -} - -void SolveSpace::SetAutosaveTimerFor(int minutes) { - Schedule(@selector(doAutosave), minutes * 60.0); -} - -void SolveSpace::ScheduleLater() { - [[NSRunLoop currentRunLoop] - performSelector:@selector(runLater:) - target:[DeferredHandler class] argument:nil - order:0 modes:@[NSDefaultRunLoopMode]]; -} - -/* OpenGL view */ - -@interface GLViewWithEditor : NSView -- (void)drawGL; - -@property BOOL wantsBackingStoreScaling; - -@property(readonly, getter=isEditing) BOOL editing; -- (void)startEditing:(NSString*)text at:(NSPoint)origin withHeight:(double)fontHeight - usingMonospace:(BOOL)isMonospace; -- (void)stopEditing; -- (void)didEdit:(NSString*)text; -@end - -@implementation GLViewWithEditor -{ - GLOffscreen *offscreen; - NSOpenGLContext *glContext; -@protected - NSTextField *editor; -} - -- initWithFrame:(NSRect)frameRect { - self = [super initWithFrame:frameRect]; - [self setWantsLayer:YES]; - - NSOpenGLPixelFormatAttribute attrs[] = { - NSOpenGLPFAColorSize, 24, - NSOpenGLPFADepthSize, 24, - 0 - }; - NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; - glContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:NULL]; - - editor = [[NSTextField alloc] init]; - [editor setEditable:YES]; - [[editor cell] setWraps:NO]; - [[editor cell] setScrollable:YES]; - [editor setBezeled:NO]; - [editor setTarget:self]; - [editor setAction:@selector(editorAction:)]; - - return self; -} - -- (void)dealloc { - delete offscreen; -} - -#define CONVERT1(name, to_from) \ - - (NS##name)convert##name##to_from##Backing:(NS##name)input { \ - return _wantsBackingStoreScaling ? [super convert##name##to_from##Backing:input] : input; } -#define CONVERT(name) CONVERT1(name, To) CONVERT1(name, From) -CONVERT(Size) -CONVERT(Rect) -#undef CONVERT -#undef CONVERT1 - -- (NSPoint)convertPointToBacking:(NSPoint)input { - if(_wantsBackingStoreScaling) return [super convertPointToBacking:input]; - else { - input.y *= -1; - return input; - } -} - -- (NSPoint)convertPointFromBacking:(NSPoint)input { - if(_wantsBackingStoreScaling) return [super convertPointFromBacking:input]; - else { - input.y *= -1; - return input; - } -} - -- (void)drawRect:(NSRect)aRect { - [glContext makeCurrentContext]; - - if(!offscreen) - offscreen = new GLOffscreen; - - NSSize size = [self convertSizeToBacking:[self bounds].size]; - offscreen->begin(size.width, size.height); - - [self drawGL]; - GL_CHECK(); - - uint8_t *pixels = offscreen->end(![self isFlipped]); - CGDataProviderRef provider = CGDataProviderCreateWithData( - NULL, pixels, size.width * size.height * 4, NULL); - CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); - CGImageRef image = CGImageCreate(size.width, size.height, 8, 32, - size.width * 4, colorspace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, - provider, NULL, true, kCGRenderingIntentDefault); - - CGContextDrawImage((CGContextRef) [[NSGraphicsContext currentContext] graphicsPort], - [self bounds], image); - - CGImageRelease(image); - CGDataProviderRelease(provider); -} - -- (void)drawGL { -} - -@synthesize editing; - -- (void)startEditing:(NSString*)text at:(NSPoint)origin withHeight:(double)fontHeight - usingMonospace:(BOOL)isMonospace { - if(!self->editing) { - [self addSubview:editor]; - self->editing = YES; - } - - NSFont *font; - if(isMonospace) - font = [NSFont fontWithName:@"Monaco" size:fontHeight]; - else - font = [NSFont controlContentFontOfSize:fontHeight]; - [editor setFont:font]; - - origin.x -= 3; /* left padding; no way to get it from NSTextField */ - origin.y -= [editor intrinsicContentSize].height; - origin.y += [editor baselineOffsetFromBottom]; - - [editor setFrameOrigin:origin]; - [editor setStringValue:text]; - [[self window] makeFirstResponder:editor]; -} - -- (void)stopEditing { - if(self->editing) { - [editor removeFromSuperview]; - self->editing = NO; - } -} - -- (void)editorAction:(id)sender { - [self didEdit:[editor stringValue]]; - [self stopEditing]; -} - -- (void)didEdit:(NSString*)text { -} -@end - -/* Graphics window */ - -@interface GraphicsWindowView : GLViewWithEditor -{ - NSTrackingArea *trackingArea; -} - -@property(readonly) NSEvent *lastContextMenuEvent; -@end - -@implementation GraphicsWindowView -- (BOOL)isFlipped { - return YES; -} - -- (void)drawGL { - SolveSpace::SS.GW.Paint(); -} - -- (BOOL)acceptsFirstResponder { - return YES; -} - -- (void) createTrackingArea { - trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] - options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | - NSTrackingActiveInKeyWindow) - owner:self userInfo:nil]; - [self addTrackingArea:trackingArea]; -} - -- (void) updateTrackingAreas -{ - [self removeTrackingArea:trackingArea]; - [self createTrackingArea]; - [super updateTrackingAreas]; -} - -- (void)mouseMoved:(NSEvent*)event { - NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]]; - NSUInteger flags = [event modifierFlags]; - NSUInteger buttons = [NSEvent pressedMouseButtons]; - SolveSpace::SS.GW.MouseMoved(point.x, point.y, - buttons & (1 << 0), - buttons & (1 << 2), - buttons & (1 << 1), - flags & NSShiftKeyMask, - flags & NSCommandKeyMask); -} - -- (void)mouseDragged:(NSEvent*)event { - [self mouseMoved:event]; -} - -- (void)rightMouseDragged:(NSEvent*)event { - [self mouseMoved:event]; -} - -- (void)otherMouseDragged:(NSEvent*)event { - [self mouseMoved:event]; -} - -- (void)mouseDown:(NSEvent*)event { - NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]]; - if([event clickCount] == 1) - SolveSpace::SS.GW.MouseLeftDown(point.x, point.y); - else if([event clickCount] == 2) - SolveSpace::SS.GW.MouseLeftDoubleClick(point.x, point.y); -} - -- (void)rightMouseDown:(NSEvent*)event { - NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]]; - SolveSpace::SS.GW.MouseMiddleOrRightDown(point.x, point.y); -} - -- (void)otherMouseDown:(NSEvent*)event { - [self rightMouseDown:event]; -} - -- (void)mouseUp:(NSEvent*)event { - NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]]; - SolveSpace::SS.GW.MouseLeftUp(point.x, point.y); -} - -- (void)rightMouseUp:(NSEvent*)event { - NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]]; - self->_lastContextMenuEvent = event; - SolveSpace::SS.GW.MouseRightUp(point.x, point.y); -} - -- (void)scrollWheel:(NSEvent*)event { - NSPoint point = [self ij_to_xy:[self convertPoint:[event locationInWindow] fromView:nil]]; - SolveSpace::SS.GW.MouseScroll(point.x, point.y, -[event deltaY]); -} - -- (void)mouseExited:(NSEvent*)event { - SolveSpace::SS.GW.MouseLeave(); -} - -- (void)keyDown:(NSEvent*)event { - int chr = 0; - if(NSString *nsChr = [event charactersIgnoringModifiers]) - chr = [nsChr characterAtIndex:0]; - - if(chr >= NSF1FunctionKey && chr <= NSF12FunctionKey) - chr = SolveSpace::GraphicsWindow::FUNCTION_KEY_BASE + (chr - NSF1FunctionKey); - - NSUInteger flags = [event modifierFlags]; - if(flags & NSShiftKeyMask) - chr |= SolveSpace::GraphicsWindow::SHIFT_MASK; - if(flags & NSCommandKeyMask) - chr |= SolveSpace::GraphicsWindow::CTRL_MASK; - - // override builtin behavior: "focus on next cell", "close window" - if(chr == '\t' || chr == '\x1b') - [[NSApp mainMenu] performKeyEquivalent:event]; - else if(!chr || !SolveSpace::SS.GW.KeyDown(chr)) - [super keyDown:event]; -} - -- (void)startEditing:(NSString*)text at:(NSPoint)xy withHeight:(double)fontHeight - withMinWidthInChars:(int)minWidthChars { - // Convert to ij (vs. xy) style coordinates - NSSize size = [self convertSizeToBacking:[self bounds].size]; - NSPoint point = { - .x = xy.x + size.width / 2, - .y = xy.y - size.height / 2 - }; - [[self window] makeKeyWindow]; - [super startEditing:text at:[self convertPointFromBacking:point] - withHeight:fontHeight usingMonospace:FALSE]; - [self prepareEditorWithMinWidthInChars:minWidthChars]; -} - -- (void)prepareEditorWithMinWidthInChars:(int)minWidthChars { - NSFont *font = [editor font]; - NSGlyph glyphA = [font glyphWithName:@"a"]; - if(glyphA == -1) oops(); - CGFloat glyphAWidth = [font advancementForGlyph:glyphA].width; - - [editor sizeToFit]; - - NSSize frameSize = [editor frame].size; - frameSize.width = std::max(frameSize.width, glyphAWidth * minWidthChars); - [editor setFrameSize:frameSize]; -} - -- (void)didEdit:(NSString*)text { - SolveSpace::SS.GW.EditControlDone([text UTF8String]); - [self setNeedsDisplay:YES]; -} - -- (void)cancelOperation:(id)sender { - [self stopEditing]; -} - -- (NSPoint)ij_to_xy:(NSPoint)ij { - // Convert to xy (vs. ij) style coordinates, - // with (0, 0) at center - NSSize size = [self bounds].size; - return [self convertPointToBacking:(NSPoint){ - .x = ij.x - size.width / 2, .y = ij.y - size.height / 2 }]; -} -@end - -@interface GraphicsWindowDelegate : NSObject -- (BOOL)windowShouldClose:(id)sender; - -@property(readonly, getter=isFullscreen) BOOL fullscreen; -- (void)windowDidEnterFullScreen:(NSNotification *)notification; -- (void)windowDidExitFullScreen:(NSNotification *)notification; -@end - -@implementation GraphicsWindowDelegate -- (BOOL)windowShouldClose:(id)sender { - [NSApp terminate:sender]; - return FALSE; /* in case NSApp changes its mind */ -} - -@synthesize fullscreen; -- (void)windowDidEnterFullScreen:(NSNotification *)notification { - fullscreen = true; - /* Update the menus */ - SolveSpace::SS.GW.EnsureValidActives(); -} -- (void)windowDidExitFullScreen:(NSNotification *)notification { - fullscreen = false; - /* Update the menus */ - SolveSpace::SS.GW.EnsureValidActives(); -} -@end - -static NSWindow *GW; -static GraphicsWindowView *GWView; -static GraphicsWindowDelegate *GWDelegate; - -namespace SolveSpace { -void InitGraphicsWindow() { - GW = [[NSWindow alloc] init]; - GWDelegate = [[GraphicsWindowDelegate alloc] init]; - [GW setDelegate:GWDelegate]; - [GW setStyleMask:(NSTitledWindowMask | NSClosableWindowMask | - NSMiniaturizableWindowMask | NSResizableWindowMask)]; - [GW setFrameAutosaveName:@"GraphicsWindow"]; - [GW setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - if(![GW setFrameUsingName:[GW frameAutosaveName]]) - [GW setContentSize:(NSSize){ .width = 600, .height = 600 }]; - GWView = [[GraphicsWindowView alloc] init]; - [GW setContentView:GWView]; -} - -void GetGraphicsWindowSize(int *w, int *h) { - NSSize size = [GWView convertSizeToBacking:[GWView frame].size]; - *w = size.width; - *h = size.height; -} - -void InvalidateGraphics(void) { - [GWView setNeedsDisplay:YES]; -} - -void PaintGraphics(void) { - [GWView setNeedsDisplay:YES]; - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES); -} - -void SetCurrentFilename(const std::string &filename) { - if(!filename.empty()) { - [GW setTitleWithRepresentedFilename:[NSString stringWithUTF8String:filename.c_str()]]; - } else { - [GW setTitle:@"(new sketch)"]; - [GW setRepresentedFilename:@""]; - } -} - -void ToggleFullScreen(void) { - [GW toggleFullScreen:nil]; -} - -bool FullScreenIsActive(void) { - return [GWDelegate isFullscreen]; -} - -void ShowGraphicsEditControl(int x, int y, int fontHeight, int minWidthChars, - const std::string &str) { - [GWView startEditing:[NSString stringWithUTF8String:str.c_str()] - at:(NSPoint){(CGFloat)x, (CGFloat)y} - withHeight:fontHeight - withMinWidthInChars:minWidthChars]; -} - -void HideGraphicsEditControl(void) { - [GWView stopEditing]; -} - -bool GraphicsEditControlIsVisible(void) { - return [GWView isEditing]; -} -} - -/* Context menus */ - -static int contextMenuChoice; - -@interface ContextMenuResponder : NSObject -+ (void)handleClick:(id)sender; -@end - -@implementation ContextMenuResponder -+ (void)handleClick:(id)sender { - contextMenuChoice = [sender tag]; -} -@end - -namespace SolveSpace { -NSMenu *contextMenu, *contextSubmenu; - -void AddContextMenuItem(const char *label, int id_) { - NSMenuItem *menuItem; - if(label) { - menuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label] - action:@selector(handleClick:) keyEquivalent:@""]; - [menuItem setTarget:[ContextMenuResponder class]]; - [menuItem setTag:id_]; - } else { - menuItem = [NSMenuItem separatorItem]; - } - - if(id_ == CONTEXT_SUBMENU) { - [menuItem setSubmenu:contextSubmenu]; - contextSubmenu = nil; - } - - if(contextSubmenu) { - [contextSubmenu addItem:menuItem]; - } else { - if(!contextMenu) { - contextMenu = [[NSMenu alloc] - initWithTitle:[NSString stringWithUTF8String:label]]; - } - - [contextMenu addItem:menuItem]; - } -} - -void CreateContextSubmenu(void) { - if(contextSubmenu) oops(); - - contextSubmenu = [[NSMenu alloc] initWithTitle:@""]; -} - -int ShowContextMenu(void) { - if(!contextMenu) - return -1; - - [NSMenu popUpContextMenu:contextMenu - withEvent:[GWView lastContextMenuEvent] forView:GWView]; - - contextMenu = nil; - - return contextMenuChoice; -} -}; - -/* Main menu */ - -@interface MainMenuResponder : NSObject -+ (void)handleStatic:(id)sender; -+ (void)handleRecent:(id)sender; -@end - -@implementation MainMenuResponder -+ (void)handleStatic:(id)sender { - SolveSpace::GraphicsWindow::MenuEntry *entry = - (SolveSpace::GraphicsWindow::MenuEntry*)[sender tag]; - - if(entry->fn && ![(NSMenuItem*)sender hasSubmenu]) - entry->fn(entry->id); -} - -+ (void)handleRecent:(id)sender { - int id_ = [sender tag]; - if(id_ >= RECENT_OPEN && id_ < (RECENT_OPEN + MAX_RECENT)) - SolveSpace::SolveSpaceUI::MenuFile(id_); - else if(id_ >= RECENT_LINK && id_ < (RECENT_LINK + MAX_RECENT)) - SolveSpace::Group::MenuGroup(id_); -} -@end - -namespace SolveSpace { -std::map mainMenuItems; - -void InitMainMenu(NSMenu *mainMenu) { - NSMenuItem *menuItem = NULL; - NSMenu *levels[5] = {mainMenu, 0}; - NSString *label; - - const GraphicsWindow::MenuEntry *entry = &GraphicsWindow::menu[0]; - int current_level = 0; - while(entry->level >= 0) { - if(entry->level > current_level) { - NSMenu *menu = [[NSMenu alloc] initWithTitle:label]; - [menu setAutoenablesItems:NO]; - [menuItem setSubmenu:menu]; - - if(entry->level >= sizeof(levels) / sizeof(levels[0])) - oops(); - - levels[entry->level] = menu; - } - - current_level = entry->level; - - if(entry->label) { - /* OS X does not support mnemonics */ - label = [[NSString stringWithUTF8String:entry->label] - stringByReplacingOccurrencesOfString:@"&" withString:@""]; - - unichar accelChar = entry->accel & - ~(GraphicsWindow::SHIFT_MASK | GraphicsWindow::CTRL_MASK); - if(accelChar > GraphicsWindow::FUNCTION_KEY_BASE && - accelChar <= GraphicsWindow::FUNCTION_KEY_BASE + 12) { - accelChar = NSF1FunctionKey + (accelChar - GraphicsWindow::FUNCTION_KEY_BASE - 1); - } else if(accelChar == GraphicsWindow::DELETE_KEY) { - accelChar = NSBackspaceCharacter; - } - NSString *accel = [NSString stringWithCharacters:&accelChar length:1]; - - menuItem = [levels[entry->level] addItemWithTitle:label - action:NULL keyEquivalent:[accel lowercaseString]]; - - NSUInteger modifierMask = 0; - if(entry->accel & GraphicsWindow::SHIFT_MASK) - modifierMask |= NSShiftKeyMask; - else if(entry->accel & GraphicsWindow::CTRL_MASK) - modifierMask |= NSCommandKeyMask; - [menuItem setKeyEquivalentModifierMask:modifierMask]; - - [menuItem setTag:(NSInteger)entry]; - [menuItem setTarget:[MainMenuResponder class]]; - [menuItem setAction:@selector(handleStatic:)]; - } else { - [levels[entry->level] addItem:[NSMenuItem separatorItem]]; - } - - mainMenuItems[entry->id] = menuItem; - - ++entry; - } -} - -void EnableMenuById(int id_, bool enabled) { - [mainMenuItems[id_] setEnabled:enabled]; -} - -void CheckMenuById(int id_, bool checked) { - [mainMenuItems[id_] setState:(checked ? NSOnState : NSOffState)]; -} - -void RadioMenuById(int id_, bool selected) { - CheckMenuById(id_, selected); -} - -static void RefreshRecentMenu(int id_, int base) { - NSMenuItem *recent = mainMenuItems[id_]; - NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; - [recent setSubmenu:menu]; - - if(std::string(RecentFile[0]).empty()) { - NSMenuItem *placeholder = [[NSMenuItem alloc] - initWithTitle:@"(no recent files)" action:nil keyEquivalent:@""]; - [placeholder setEnabled:NO]; - [menu addItem:placeholder]; - } else { - for(int i = 0; i < MAX_RECENT; i++) { - if(std::string(RecentFile[i]).empty()) - break; - - NSMenuItem *item = [[NSMenuItem alloc] - initWithTitle:[[NSString stringWithUTF8String:RecentFile[i].c_str()] - stringByAbbreviatingWithTildeInPath] - action:nil keyEquivalent:@""]; - [item setTag:(base + i)]; - [item setAction:@selector(handleRecent:)]; - [item setTarget:[MainMenuResponder class]]; - [menu addItem:item]; - } - } -} - -void RefreshRecentMenus(void) { - RefreshRecentMenu(GraphicsWindow::MNU_OPEN_RECENT, RECENT_OPEN); - RefreshRecentMenu(GraphicsWindow::MNU_GROUP_RECENT, RECENT_LINK); -} - -void ToggleMenuBar(void) { - [NSMenu setMenuBarVisible:![NSMenu menuBarVisible]]; -} - -bool MenuBarIsVisible(void) { - return [NSMenu menuBarVisible]; -} -} - -/* Save/load */ - -bool SolveSpace::GetOpenFile(std::string *file, const std::string &defExtension, - const FileFilter ssFilters[]) { - NSOpenPanel *panel = [NSOpenPanel openPanel]; - NSMutableArray *filters = [[NSMutableArray alloc] init]; - for(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) { - for(const char *const *ssPattern = ssFilter->patterns; *ssPattern; ssPattern++) { - [filters addObject:[NSString stringWithUTF8String:*ssPattern]]; - } - } - [filters removeObjectIdenticalTo:@"*"]; - [panel setAllowedFileTypes:filters]; - - if([panel runModal] == NSFileHandlingPanelOKButton) { - *file = [[NSFileManager defaultManager] - fileSystemRepresentationWithPath:[[panel URL] path]]; - return true; - } else { - return false; - } -} - -@interface SaveFormatController : NSViewController -@property NSSavePanel *panel; -@property NSArray *extensions; -@property (nonatomic) IBOutlet NSPopUpButton *button; -@property (nonatomic) NSInteger index; -@end - -@implementation SaveFormatController -@synthesize panel, extensions, button, index; -- (void)setIndex:(NSInteger)newIndex { - self->index = newIndex; - NSString *extension = [extensions objectAtIndex:newIndex]; - if(![extension isEqual:@"*"]) { - NSString *filename = [panel nameFieldStringValue]; - NSString *basename = [[filename componentsSeparatedByString:@"."] objectAtIndex:0]; - [panel setNameFieldStringValue:[basename stringByAppendingPathExtension:extension]]; - } -} -@end - -bool SolveSpace::GetSaveFile(std::string *file, const std::string &defExtension, - const FileFilter ssFilters[]) { - NSSavePanel *panel = [NSSavePanel savePanel]; - - SaveFormatController *controller = - [[SaveFormatController alloc] initWithNibName:@"SaveFormatAccessory" bundle:nil]; - [controller setPanel:panel]; - [panel setAccessoryView:[controller view]]; - - NSMutableArray *extensions = [[NSMutableArray alloc] init]; - [controller setExtensions:extensions]; - - NSPopUpButton *button = [controller button]; - [button removeAllItems]; - for(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) { - std::string desc; - for(const char *const *ssPattern = ssFilter->patterns; *ssPattern; ssPattern++) { - if(desc == "") { - desc = *ssPattern; - } else { - desc += ", "; - desc += *ssPattern; - } - } - std::string title = std::string(ssFilter->name) + " (" + desc + ")"; - [button addItemWithTitle:[NSString stringWithUTF8String:title.c_str()]]; - [extensions addObject:[NSString stringWithUTF8String:ssFilter->patterns[0]]]; - } - - int extensionIndex = 0; - if(defExtension != "") { - extensionIndex = [extensions indexOfObject: - [NSString stringWithUTF8String:defExtension.c_str()]]; - if(extensionIndex == -1) { - extensionIndex = 0; - } - } - - [button selectItemAtIndex:extensionIndex]; - [panel setNameFieldStringValue:[@"untitled" - stringByAppendingPathExtension:[extensions objectAtIndex:extensionIndex]]]; - - if([panel runModal] == NSFileHandlingPanelOKButton) { - *file = [[NSFileManager defaultManager] - fileSystemRepresentationWithPath:[[panel URL] path]]; - return true; - } else { - return false; - } -} - -SolveSpace::DialogChoice SolveSpace::SaveFileYesNoCancel(void) { - NSAlert *alert = [[NSAlert alloc] init]; - if(!std::string(SolveSpace::SS.saveFile).empty()) { - [alert setMessageText: - [[@"Do you want to save the changes you made to the sketch “" - stringByAppendingString: - [[NSString stringWithUTF8String:SolveSpace::SS.saveFile.c_str()] - stringByAbbreviatingWithTildeInPath]] - stringByAppendingString:@"”?"]]; - } else { - [alert setMessageText:@"Do you want to save the changes you made to the new sketch?"]; - } - [alert setInformativeText:@"Your changes will be lost if you don't save them."]; - [alert addButtonWithTitle:@"Save"]; - [alert addButtonWithTitle:@"Cancel"]; - [alert addButtonWithTitle:@"Don't Save"]; - switch([alert runModal]) { - case NSAlertFirstButtonReturn: - return DIALOG_YES; - case NSAlertSecondButtonReturn: - default: - return DIALOG_CANCEL; - case NSAlertThirdButtonReturn: - return DIALOG_NO; - } -} - -SolveSpace::DialogChoice SolveSpace::LoadAutosaveYesNo(void) { - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText: - @"An autosave file is availible for this project."]; - [alert setInformativeText: - @"Do you want to load the autosave file instead?"]; - [alert addButtonWithTitle:@"Load"]; - [alert addButtonWithTitle:@"Don't Load"]; - switch([alert runModal]) { - case NSAlertFirstButtonReturn: - return DIALOG_YES; - case NSAlertSecondButtonReturn: - default: - return DIALOG_NO; - } -} - -SolveSpace::DialogChoice SolveSpace::LocateImportedFileYesNoCancel( - const std::string &filename, bool canCancel) { - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:[NSString stringWithUTF8String: - ("The linked file " + filename + " is not present.").c_str()]]; - [alert setInformativeText: - @"Do you want to locate it manually?\n" - "If you select \"No\", any geometry that depends on " - "the missing file will be removed."]; - [alert addButtonWithTitle:@"Yes"]; - if(canCancel) - [alert addButtonWithTitle:@"Cancel"]; - [alert addButtonWithTitle:@"No"]; - switch([alert runModal]) { - case NSAlertFirstButtonReturn: - return DIALOG_YES; - case NSAlertSecondButtonReturn: - default: - if(canCancel) - return DIALOG_CANCEL; - /* fallthrough */ - case NSAlertThirdButtonReturn: - return DIALOG_NO; - } -} - -/* Text window */ - -@interface TextWindowView : GLViewWithEditor -{ - NSTrackingArea *trackingArea; -} - -@property (nonatomic, getter=isCursorHand) BOOL cursorHand; -@end - -@implementation TextWindowView -- (BOOL)isFlipped { - return YES; -} - -- (void)drawGL { - SolveSpace::SS.TW.Paint(); -} - -- (BOOL)acceptsFirstMouse:(NSEvent*)event { - return YES; -} - -- (void) createTrackingArea { - trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] - options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | - NSTrackingActiveAlways) - owner:self userInfo:nil]; - [self addTrackingArea:trackingArea]; -} - -- (void) updateTrackingAreas -{ - [self removeTrackingArea:trackingArea]; - [self createTrackingArea]; - [super updateTrackingAreas]; -} - -- (void)mouseMoved:(NSEvent*)event { - NSPoint point = [self convertPointToBacking: - [self convertPoint:[event locationInWindow] fromView:nil]]; - SolveSpace::SS.TW.MouseEvent(/*leftClick*/ false, /*leftDown*/ false, - point.x, -point.y); -} - -- (void)mouseDown:(NSEvent*)event { - NSPoint point = [self convertPointToBacking: - [self convertPoint:[event locationInWindow] fromView:nil]]; - SolveSpace::SS.TW.MouseEvent(/*leftClick*/ true, /*leftDown*/ true, - point.x, -point.y); -} - -- (void)mouseDragged:(NSEvent*)event { - NSPoint point = [self convertPointToBacking: - [self convertPoint:[event locationInWindow] fromView:nil]]; - SolveSpace::SS.TW.MouseEvent(/*leftClick*/ false, /*leftDown*/ true, - point.x, -point.y); -} - -- (void)setCursorHand:(BOOL)cursorHand { - if(_cursorHand != cursorHand) { - if(cursorHand) - [[NSCursor pointingHandCursor] push]; - else - [NSCursor pop]; - } - _cursorHand = cursorHand; -} - -- (void)mouseExited:(NSEvent*)event { - [self setCursorHand:FALSE]; - SolveSpace::SS.TW.MouseLeave(); -} - -- (void)startEditing:(NSString*)text at:(NSPoint)point { - point = [self convertPointFromBacking:point]; - point.y = -point.y + 2; - [[self window] makeKeyWindow]; - [super startEditing:text at:point withHeight:15.0 usingMonospace:TRUE]; - [editor setFrameSize:(NSSize){ - .width = [self bounds].size.width - [editor frame].origin.x, - .height = [editor intrinsicContentSize].height }]; -} - -- (void)stopEditing { - [super stopEditing]; - [GW makeKeyWindow]; -} - -- (void)didEdit:(NSString*)text { - SolveSpace::SS.TW.EditControlDone([text UTF8String]); -} - -- (void)cancelOperation:(id)sender { - [self stopEditing]; -} -@end - -@interface TextWindowDelegate : NSObject -- (BOOL)windowShouldClose:(id)sender; -- (void)windowDidResize:(NSNotification *)notification; -@end - -@implementation TextWindowDelegate -- (BOOL)windowShouldClose:(id)sender { - SolveSpace::GraphicsWindow::MenuView(SolveSpace::GraphicsWindow::MNU_SHOW_TEXT_WND); - return NO; -} - -- (void)windowDidResize:(NSNotification *)notification { - NSClipView *view = [[[notification object] contentView] contentView]; - NSView *document = [view documentView]; - NSSize size = [document frame].size; - size.width = [view frame].size.width; - [document setFrameSize:size]; -} -@end - -static NSPanel *TW; -static TextWindowView *TWView; -static TextWindowDelegate *TWDelegate; - -namespace SolveSpace { -void InitTextWindow() { - TW = [[NSPanel alloc] init]; - TWDelegate = [[TextWindowDelegate alloc] init]; - [TW setStyleMask:(NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | - NSUtilityWindowMask)]; - [[TW standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; - [[TW standardWindowButton:NSWindowZoomButton] setHidden:YES]; - [TW setTitle:@"Property Browser"]; - [TW setFrameAutosaveName:@"TextWindow"]; - [TW setFloatingPanel:YES]; - [TW setBecomesKeyOnlyIfNeeded:YES]; - - NSScrollView *scrollView = [[NSScrollView alloc] init]; - [TW setContentView:scrollView]; - [scrollView setBackgroundColor:[NSColor blackColor]]; - [scrollView setHasVerticalScroller:YES]; - [scrollView setScrollerKnobStyle:NSScrollerKnobStyleLight]; - [[scrollView contentView] setCopiesOnScroll:YES]; - - TWView = [[TextWindowView alloc] init]; - [scrollView setDocumentView:TWView]; - - [TW setDelegate:TWDelegate]; - if(![TW setFrameUsingName:[TW frameAutosaveName]]) - [TW setContentSize:(NSSize){ .width = 420, .height = 300 }]; - [TWView setFrame:[[scrollView contentView] frame]]; -} - -void ShowTextWindow(bool visible) { - if(visible) - [TW orderFront:nil]; - else - [TW close]; -} - -void GetTextWindowSize(int *w, int *h) { - NSSize size = [TWView convertSizeToBacking:[TWView frame].size]; - *w = size.width; - *h = size.height; -} - -void InvalidateText(void) { - NSSize size = [TWView convertSizeToBacking:[TWView frame].size]; - size.height = (SS.TW.top[SS.TW.rows - 1] + 1) * TextWindow::LINE_HEIGHT / 2; - [TWView setFrameSize:[TWView convertSizeFromBacking:size]]; - [TWView setNeedsDisplay:YES]; -} - -void MoveTextScrollbarTo(int pos, int maxPos, int page) { - /* unused; we draw the entire text window and scroll in Cocoa */ -} - -void SetMousePointerToHand(bool is_hand) { - [TWView setCursorHand:is_hand]; -} - -void ShowTextEditControl(int x, int y, const std::string &str) { - return [TWView startEditing:[NSString stringWithUTF8String:str.c_str()] - at:(NSPoint){(CGFloat)x, (CGFloat)y}]; -} - -void HideTextEditControl(void) { - return [TWView stopEditing]; -} - -bool TextEditControlIsVisible(void) { - return [TWView isEditing]; -} -}; - -/* Miscellanea */ - -void SolveSpace::DoMessageBox(const char *str, int rows, int cols, bool error) { - NSAlert *alert = [[NSAlert alloc] init]; - [alert setAlertStyle:(error ? NSWarningAlertStyle : NSInformationalAlertStyle)]; - [alert addButtonWithTitle:@"OK"]; - - /* do some additional formatting of the message these are - heuristics, but they are made failsafe and lead to nice results. */ - NSString *input = [NSString stringWithUTF8String:str]; - NSRange dot = [input rangeOfCharacterFromSet: - [NSCharacterSet characterSetWithCharactersInString:@".:"]]; - if(dot.location != NSNotFound) { - [alert setMessageText:[[input substringToIndex:dot.location + 1] - stringByReplacingOccurrencesOfString:@"\n" withString:@" "]]; - [alert setInformativeText: - [[input substringFromIndex:dot.location + 1] - stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]]; - } else { - [alert setMessageText:[input - stringByReplacingOccurrencesOfString:@"\n" withString:@" "]]; - } - - [alert runModal]; -} - -void SolveSpace::OpenWebsite(const char *url) { - [[NSWorkspace sharedWorkspace] openURL: - [NSURL URLWithString:[NSString stringWithUTF8String:url]]]; -} - -std::vector SolveSpace::GetFontFiles() { - std::vector fonts; - - NSArray *fontNames = [[NSFontManager sharedFontManager] availableFonts]; - for(NSString *fontName in fontNames) { - CTFontDescriptorRef fontRef = - CTFontDescriptorCreateWithNameAndSize ((__bridge CFStringRef)fontName, 10.0); - CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fontRef, kCTFontURLAttribute); - NSString *fontPath = [NSString stringWithString:[(NSURL *)CFBridgingRelease(url) path]]; - fonts.push_back([[NSFileManager defaultManager] - fileSystemRepresentationWithPath:fontPath]); - } - - return fonts; -} - -/* Application lifecycle */ - -@interface ApplicationDelegate : NSObject -- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication; -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; -- (void)applicationWillTerminate:(NSNotification *)aNotification; -- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; -- (IBAction)preferences:(id)sender; -@end - -@implementation ApplicationDelegate -- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { - return YES; -} - -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - if(SolveSpace::SS.OkayToStartNewFile()) - return NSTerminateNow; - else - return NSTerminateCancel; -} - -- (void)applicationWillTerminate:(NSNotification *)aNotification { - SolveSpace::SS.Exit(); -} - -- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { - return SolveSpace::SS.OpenFile([filename UTF8String]); -} - -- (IBAction)preferences:(id)sender { - SolveSpace::SS.TW.GoToScreen(SolveSpace::TextWindow::SCREEN_CONFIGURATION); - SolveSpace::SS.ScheduleShowTW(); -} -@end - -void SolveSpace::ExitNow(void) { - [NSApp stop:nil]; -} - -/* - * Normally we would just link to the 3DconnexionClient framework. - * We don't want to (are not allowed to) distribute the official - * framework, so we're trying to use the one installed on the users - * computer. There are some different versions of the framework, - * the official one and re-implementations using an open source driver - * for older devices (spacenav-plus). So weak-linking isn't an option, - * either. The only remaining way is using CFBundle to dynamically - * load the library at runtime, and also detect its availability. - * - * We're also defining everything needed from the 3DconnexionClientAPI, - * so we're not depending on the API headers. - */ - -#pragma pack(push,2) - -enum { - kConnexionClientModeTakeOver = 1, - kConnexionClientModePlugin = 2 -}; - -#define kConnexionMsgDeviceState '3dSR' -#define kConnexionMaskButtons 0x00FF -#define kConnexionMaskAxis 0x3F00 - -typedef struct { - uint16_t version; - uint16_t client; - uint16_t command; - int16_t param; - int32_t value; - UInt64 time; - uint8_t report[8]; - uint16_t buttons8; - int16_t axis[6]; - uint16_t address; - uint32_t buttons; -} ConnexionDeviceState, *ConnexionDeviceStatePtr; - -#pragma pack(pop) - -typedef void (*ConnexionAddedHandlerProc)(io_connect_t); -typedef void (*ConnexionRemovedHandlerProc)(io_connect_t); -typedef void (*ConnexionMessageHandlerProc)(io_connect_t, natural_t, void *); - -typedef OSErr (*InstallConnexionHandlersProc)(ConnexionMessageHandlerProc, ConnexionAddedHandlerProc, ConnexionRemovedHandlerProc); -typedef void (*CleanupConnexionHandlersProc)(void); -typedef UInt16 (*RegisterConnexionClientProc)(UInt32, UInt8 *, UInt16, UInt32); -typedef void (*UnregisterConnexionClientProc)(UInt16); - -static BOOL connexionShiftIsDown = NO; -static UInt16 connexionClient = 0; -static UInt32 connexionSignature = 'SoSp'; -static UInt8 *connexionName = (UInt8 *)"SolveSpace"; -static CFBundleRef spaceBundle = NULL; -static InstallConnexionHandlersProc installConnexionHandlers = NULL; -static CleanupConnexionHandlersProc cleanupConnexionHandlers = NULL; -static RegisterConnexionClientProc registerConnexionClient = NULL; -static UnregisterConnexionClientProc unregisterConnexionClient = NULL; - -static void connexionAdded(io_connect_t con) {} -static void connexionRemoved(io_connect_t con) {} -static void connexionMessage(io_connect_t con, natural_t type, void *arg) { - if (type != kConnexionMsgDeviceState) { - return; - } - - ConnexionDeviceState *device = (ConnexionDeviceState *)arg; - - dispatch_async(dispatch_get_main_queue(), ^(void){ - SolveSpace::SS.GW.SpaceNavigatorMoved( - (double)device->axis[0] * -0.25, - (double)device->axis[1] * -0.25, - (double)device->axis[2] * 0.25, - (double)device->axis[3] * -0.0005, - (double)device->axis[4] * -0.0005, - (double)device->axis[5] * -0.0005, - (connexionShiftIsDown == YES) ? 1 : 0 - ); - }); -} - -static void connexionInit() { - NSString *bundlePath = @"/Library/Frameworks/3DconnexionClient.framework"; - NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath]; - spaceBundle = CFBundleCreate(kCFAllocatorDefault, (__bridge CFURLRef)bundleURL); - - // Don't continue if no Spacemouse driver is installed on this machine - if (spaceBundle == NULL) { - return; - } - - installConnexionHandlers = (InstallConnexionHandlersProc) - CFBundleGetFunctionPointerForName(spaceBundle, - CFSTR("InstallConnexionHandlers")); - - cleanupConnexionHandlers = (CleanupConnexionHandlersProc) - CFBundleGetFunctionPointerForName(spaceBundle, - CFSTR("CleanupConnexionHandlers")); - - registerConnexionClient = (RegisterConnexionClientProc) - CFBundleGetFunctionPointerForName(spaceBundle, - CFSTR("RegisterConnexionClient")); - - unregisterConnexionClient = (UnregisterConnexionClientProc) - CFBundleGetFunctionPointerForName(spaceBundle, - CFSTR("UnregisterConnexionClient")); - - // Only continue if all required symbols have been loaded - if ((installConnexionHandlers == NULL) || (cleanupConnexionHandlers == NULL) - || (registerConnexionClient == NULL) || (unregisterConnexionClient == NULL)) { - CFRelease(spaceBundle); - spaceBundle = NULL; - return; - } - - installConnexionHandlers(&connexionMessage, &connexionAdded, &connexionRemoved); - connexionClient = registerConnexionClient(connexionSignature, connexionName, - kConnexionClientModeTakeOver, kConnexionMaskButtons | kConnexionMaskAxis); - - // Monitor modifier flags to detect Shift button state changes - [NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyDownMask | NSFlagsChangedMask) - handler:^(NSEvent *event) { - if (event.modifierFlags & NSShiftKeyMask) { - connexionShiftIsDown = YES; - } - return event; - }]; - - [NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyUpMask | NSFlagsChangedMask) - handler:^(NSEvent *event) { - if (!(event.modifierFlags & NSShiftKeyMask)) { - connexionShiftIsDown = NO; - } - return event; - }]; -} - -static void connexionClose() { - if (spaceBundle == NULL) { - return; - } - - unregisterConnexionClient(connexionClient); - cleanupConnexionHandlers(); - - CFRelease(spaceBundle); -} - -int main(int argc, const char *argv[]) { - [NSApplication sharedApplication]; - ApplicationDelegate *delegate = [[ApplicationDelegate alloc] init]; - [NSApp setDelegate:delegate]; - - SolveSpace::InitGraphicsWindow(); - SolveSpace::InitTextWindow(); - [[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:nil topLevelObjects:nil]; - SolveSpace::InitMainMenu([NSApp mainMenu]); - - connexionInit(); - SolveSpace::SS.Init(); - - [GW makeKeyAndOrderFront:nil]; - [NSApp run]; - - connexionClose(); - SolveSpace::SK.Clear(); - SolveSpace::SS.Clear(); - - return 0; -} diff --git a/src/config.h.in b/src/config.h.in index aca7757..144c46d 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -1,13 +1,18 @@ -#ifndef __CONFIG_H -#define __CONFIG_H +#ifndef SOLVESPACE_CONFIG_H +#define SOLVESPACE_CONFIG_H #define PACKAGE_VERSION "@solvespace_VERSION_MAJOR@.@solvespace_VERSION_MINOR@~@solvespace_GIT_HASH@" +/* Non-OS X *nix only */ +#define UNIX_DATADIR "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_DATAROOTDIR@/solvespace" + /* Do we have the si library on win32, or libspnav on *nix? */ #cmakedefine HAVE_SPACEWARE -#cmakedefine HAVE_GTK -#cmakedefine HAVE_GTK2 -#cmakedefine HAVE_GTK3 +/* What OpenGL version do we use? */ +#define HAVE_OPENGL @OPENGL@ + +/* If we use GTK, can we use the native file chooser? */ +#cmakedefine HAVE_GTK_FILECHOOSERNATIVE #endif diff --git a/src/confscreen.cpp b/src/confscreen.cpp index a7ad678..07c42b7 100644 --- a/src/confscreen.cpp +++ b/src/confscreen.cpp @@ -8,94 +8,138 @@ void TextWindow::ScreenChangeLightDirection(int link, uint32_t v) { SS.TW.ShowEditControl(8, ssprintf("%.2f, %.2f, %.2f", CO(SS.lightDir[v]))); - SS.TW.edit.meaning = EDIT_LIGHT_DIRECTION; + SS.TW.edit.meaning = Edit::LIGHT_DIRECTION; SS.TW.edit.i = v; } void TextWindow::ScreenChangeLightIntensity(int link, uint32_t v) { SS.TW.ShowEditControl(31, ssprintf("%.2f", SS.lightIntensity[v])); - SS.TW.edit.meaning = EDIT_LIGHT_INTENSITY; + SS.TW.edit.meaning = Edit::LIGHT_INTENSITY; SS.TW.edit.i = v; } +void TextWindow::ScreenChangeLightAmbient(int link, uint32_t v) { + SS.TW.ShowEditControl(31, ssprintf("%.2f", SS.ambientIntensity)); + SS.TW.edit.meaning = Edit::LIGHT_AMBIENT; + SS.TW.edit.i = 0; +} + void TextWindow::ScreenChangeColor(int link, uint32_t v) { SS.TW.ShowEditControlWithColorPicker(13, SS.modelColor[v]); - SS.TW.edit.meaning = EDIT_COLOR; + SS.TW.edit.meaning = Edit::COLOR; SS.TW.edit.i = v; } void TextWindow::ScreenChangeChordTolerance(int link, uint32_t v) { SS.TW.ShowEditControl(3, ssprintf("%lg", SS.chordTol)); - SS.TW.edit.meaning = EDIT_CHORD_TOLERANCE; + SS.TW.edit.meaning = Edit::CHORD_TOLERANCE; SS.TW.edit.i = 0; } void TextWindow::ScreenChangeMaxSegments(int link, uint32_t v) { SS.TW.ShowEditControl(3, ssprintf("%d", SS.maxSegments)); - SS.TW.edit.meaning = EDIT_MAX_SEGMENTS; + SS.TW.edit.meaning = Edit::MAX_SEGMENTS; SS.TW.edit.i = 0; } void TextWindow::ScreenChangeExportChordTolerance(int link, uint32_t v) { SS.TW.ShowEditControl(3, ssprintf("%lg", SS.exportChordTol)); - SS.TW.edit.meaning = EDIT_CHORD_TOLERANCE; + SS.TW.edit.meaning = Edit::CHORD_TOLERANCE; SS.TW.edit.i = 1; } void TextWindow::ScreenChangeExportMaxSegments(int link, uint32_t v) { SS.TW.ShowEditControl(3, ssprintf("%d", SS.exportMaxSegments)); - SS.TW.edit.meaning = EDIT_MAX_SEGMENTS; + SS.TW.edit.meaning = Edit::MAX_SEGMENTS; SS.TW.edit.i = 1; } void TextWindow::ScreenChangeCameraTangent(int link, uint32_t v) { SS.TW.ShowEditControl(3, ssprintf("%.3f", 1000*SS.cameraTangent)); - SS.TW.edit.meaning = EDIT_CAMERA_TANGENT; + SS.TW.edit.meaning = Edit::CAMERA_TANGENT; } void TextWindow::ScreenChangeGridSpacing(int link, uint32_t v) { SS.TW.ShowEditControl(3, SS.MmToString(SS.gridSpacing)); - SS.TW.edit.meaning = EDIT_GRID_SPACING; + SS.TW.edit.meaning = Edit::GRID_SPACING; } void TextWindow::ScreenChangeDigitsAfterDecimal(int link, uint32_t v) { - SS.TW.ShowEditControl(3, ssprintf("%d", SS.UnitDigitsAfterDecimal())); - SS.TW.edit.meaning = EDIT_DIGITS_AFTER_DECIMAL; + SS.TW.ShowEditControl(14, ssprintf("%d", SS.UnitDigitsAfterDecimal())); + SS.TW.edit.meaning = Edit::DIGITS_AFTER_DECIMAL; +} + +void TextWindow::ScreenChangeDigitsAfterDecimalDegree(int link, uint32_t v) { + SS.TW.ShowEditControl(14, ssprintf("%d", SS.afterDecimalDegree)); + SS.TW.edit.meaning = Edit::DIGITS_AFTER_DECIMAL_DEGREE; +} + +void TextWindow::ScreenChangeUseSIPrefixes(int link, uint32_t v) { + SS.useSIPrefixes = !SS.useSIPrefixes; + SS.GW.Invalidate(); } void TextWindow::ScreenChangeExportScale(int link, uint32_t v) { SS.TW.ShowEditControl(5, ssprintf("%.3f", (double)SS.exportScale)); - SS.TW.edit.meaning = EDIT_EXPORT_SCALE; + SS.TW.edit.meaning = Edit::EXPORT_SCALE; } void TextWindow::ScreenChangeExportOffset(int link, uint32_t v) { SS.TW.ShowEditControl(3, SS.MmToString(SS.exportOffset)); - SS.TW.edit.meaning = EDIT_EXPORT_OFFSET; + SS.TW.edit.meaning = Edit::EXPORT_OFFSET; } void TextWindow::ScreenChangeFixExportColors(int link, uint32_t v) { SS.fixExportColors = !SS.fixExportColors; } +void TextWindow::ScreenChangeExportBackgroundColor(int link, uint32_t v) { + SS.exportBackgroundColor = !SS.exportBackgroundColor; +} + void TextWindow::ScreenChangeBackFaces(int link, uint32_t v) { SS.drawBackFaces = !SS.drawBackFaces; - InvalidateGraphics(); + SS.GW.Invalidate(/*clearPersistent=*/true); +} + +void TextWindow::ScreenChangeTurntableNav(int link, uint32_t v) { + SS.turntableNav = !SS.turntableNav; + if(SS.turntableNav) { + // If turntable nav is being turned on, align view so Z is vertical + SS.GW.AnimateOnto(Quaternion::From(Vector::From(-1, 0, 0), Vector::From(0, 0, 1)), + SS.GW.offset); + } +} + +void TextWindow::ScreenChangeImmediatelyEditDimension(int link, uint32_t v) { + SS.immediatelyEditDimension = !SS.immediatelyEditDimension; + SS.GW.Invalidate(/*clearPersistent=*/true); +} + +void TextWindow::ScreenChangeShowContourAreas(int link, uint32_t v) { + SS.showContourAreas = !SS.showContourAreas; + SS.GW.Invalidate(); } void TextWindow::ScreenChangeCheckClosedContour(int link, uint32_t v) { SS.checkClosedContour = !SS.checkClosedContour; - InvalidateGraphics(); + SS.GW.Invalidate(); +} + +void TextWindow::ScreenChangeAutomaticLineConstraints(int link, uint32_t v) { + SS.automaticLineConstraints = !SS.automaticLineConstraints; + SS.GW.Invalidate(); } void TextWindow::ScreenChangeShadedTriangles(int link, uint32_t v) { SS.exportShadedTriangles = !SS.exportShadedTriangles; - InvalidateGraphics(); + SS.GW.Invalidate(); } void TextWindow::ScreenChangePwlCurves(int link, uint32_t v) { SS.exportPwlCurves = !SS.exportPwlCurves; - InvalidateGraphics(); + SS.GW.Invalidate(); } void TextWindow::ScreenChangeCanvasSizeAuto(int link, uint32_t v) { @@ -104,7 +148,7 @@ void TextWindow::ScreenChangeCanvasSizeAuto(int link, uint32_t v) { } else { SS.exportCanvasSizeAuto = false; } - InvalidateGraphics(); + SS.GW.Invalidate(); } void TextWindow::ScreenChangeCanvasSize(int link, uint32_t v) { @@ -125,7 +169,7 @@ void TextWindow::ScreenChangeCanvasSize(int link, uint32_t v) { int col = 13; if(v < 10) col = 11; SS.TW.ShowEditControl(col, SS.MmToString(d)); - SS.TW.edit.meaning = EDIT_CANVAS_SIZE; + SS.TW.edit.meaning = Edit::CANVAS_SIZE; SS.TW.edit.i = v; } @@ -133,22 +177,22 @@ void TextWindow::ScreenChangeGCodeParameter(int link, uint32_t v) { std::string buf; switch(link) { case 'd': - SS.TW.edit.meaning = EDIT_G_CODE_DEPTH; + SS.TW.edit.meaning = Edit::G_CODE_DEPTH; buf += SS.MmToString(SS.gCode.depth); break; case 's': - SS.TW.edit.meaning = EDIT_G_CODE_PASSES; + SS.TW.edit.meaning = Edit::G_CODE_PASSES; buf += std::to_string(SS.gCode.passes); break; case 'F': - SS.TW.edit.meaning = EDIT_G_CODE_FEED; + SS.TW.edit.meaning = Edit::G_CODE_FEED; buf += SS.MmToString(SS.gCode.feed); break; case 'P': - SS.TW.edit.meaning = EDIT_G_CODE_PLUNGE_FEED; + SS.TW.edit.meaning = Edit::G_CODE_PLUNGE_FEED; buf += SS.MmToString(SS.gCode.plungeFeed); break; } @@ -157,10 +201,15 @@ void TextWindow::ScreenChangeGCodeParameter(int link, uint32_t v) { void TextWindow::ScreenChangeAutosaveInterval(int link, uint32_t v) { SS.TW.ShowEditControl(3, std::to_string(SS.autosaveInterval)); - SS.TW.edit.meaning = EDIT_AUTOSAVE_INTERVAL; + SS.TW.edit.meaning = Edit::AUTOSAVE_INTERVAL; +} + +void TextWindow::ScreenChangeFindConstraintTimeout(int link, uint32_t v) { + SS.TW.ShowEditControl(3, std::to_string(SS.timeoutRedundantConstr)); + SS.TW.edit.meaning = Edit::FIND_CONSTRAINT_TIMEOUT; } -void TextWindow::ShowConfiguration(void) { +void TextWindow::ShowConfiguration() { int i; Printf(true, "%Ft user color (r, g, b)"); @@ -184,6 +233,10 @@ void TextWindow::ShowConfiguration(void) { CO(SS.lightDir[i]), i, &ScreenChangeLightDirection, SS.lightIntensity[i], i, &ScreenChangeLightIntensity); } + Printf(false, "%Bp ambient lighting " + "%2 %Fl%D%f%Ll[c]%E", + (i & 1) ? 'd' : 'a', i, + SS.ambientIntensity, &ScreenChangeLightAmbient); Printf(false, ""); Printf(false, "%Ft chord tolerance (in percents)%E"); @@ -215,11 +268,20 @@ void TextWindow::ShowConfiguration(void) { Printf(false, "%Ba %s %Fl%Ll%f%D[change]%E", SS.MmToString(SS.gridSpacing).c_str(), &ScreenChangeGridSpacing, 0); + + Printf(false, ""); Printf(false, "%Ft digits after decimal point to show%E"); - Printf(false, "%Ba %d %Fl%Ll%f%D[change]%E (e.g. '%s')", + Printf(false, "%Ba%Ft distances: %Fd%d %Fl%Ll%f%D[change]%E (e.g. '%s')", SS.UnitDigitsAfterDecimal(), &ScreenChangeDigitsAfterDecimal, 0, SS.MmToString(SS.StringToMm("1.23456789")).c_str()); + Printf(false, "%Bd%Ft angles: %Fd%d %Fl%Ll%f%D[change]%E (e.g. '%s')", + SS.afterDecimalDegree, + &ScreenChangeDigitsAfterDecimalDegree, 0, + SS.DegreeToString(1.23456789).c_str()); + Printf(false, " %Fd%f%Ll%s use SI prefixes for distances%E", + &ScreenChangeUseSIPrefixes, + SS.useSIPrefixes ? CHECK_TRUE : CHECK_FALSE); Printf(false, ""); Printf(false, "%Ft export scale factor (1:1=mm, 1:25.4=inch)"); @@ -246,6 +308,9 @@ void TextWindow::ShowConfiguration(void) { Printf(false, " %Fd%f%Ll%s fix white exported lines%E", &ScreenChangeFixExportColors, SS.fixExportColors ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %Fd%f%Ll%s export background color%E", + &ScreenChangeExportBackgroundColor, + SS.exportBackgroundColor ? CHECK_TRUE : CHECK_FALSE); Printf(false, ""); Printf(false, "%Ft export canvas size: " @@ -296,113 +361,144 @@ void TextWindow::ShowConfiguration(void) { Printf(false, " %Fd%f%Ll%s check sketch for closed contour%E", &ScreenChangeCheckClosedContour, SS.checkClosedContour ? CHECK_TRUE : CHECK_FALSE); - + Printf(false, " %Fd%f%Ll%s show areas of closed contours%E", + &ScreenChangeShowContourAreas, + SS.showContourAreas ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %Fd%f%Ll%s enable automatic line constraints%E", + &ScreenChangeAutomaticLineConstraints, + SS.automaticLineConstraints ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %Fd%f%Ll%s use turntable mouse navigation%E", &ScreenChangeTurntableNav, + SS.turntableNav ? CHECK_TRUE : CHECK_FALSE); + Printf(false, " %Fd%f%Ll%s edit newly added dimensions%E", + &ScreenChangeImmediatelyEditDimension, + SS.immediatelyEditDimension ? CHECK_TRUE : CHECK_FALSE); Printf(false, ""); Printf(false, "%Ft autosave interval (in minutes)%E"); Printf(false, "%Ba %d %Fl%Ll%f[change]%E", SS.autosaveInterval, &ScreenChangeAutosaveInterval); - Printf(false, ""); - Printf(false, " %Ftgl vendor %E%s", glGetString(GL_VENDOR)); - Printf(false, " %Ft renderer %E%s", glGetString(GL_RENDERER)); - Printf(false, " %Ft version %E%s", glGetString(GL_VERSION)); + Printf(false, "%Ft redundant constraint timeout (in ms)%E"); + Printf(false, "%Ba %d %Fl%Ll%f[change]%E", + SS.timeoutRedundantConstr, &ScreenChangeFindConstraintTimeout); + + if(canvas) { + const char *gl_vendor, *gl_renderer, *gl_version; + canvas->GetIdent(&gl_vendor, &gl_renderer, &gl_version); + Printf(false, ""); + Printf(false, " %Ftgl vendor %E%s", gl_vendor); + Printf(false, " %Ft renderer %E%s", gl_renderer); + Printf(false, " %Ft version %E%s", gl_version); + } } -bool TextWindow::EditControlDoneForConfiguration(const char *s) { +bool TextWindow::EditControlDoneForConfiguration(const std::string &s) { switch(edit.meaning) { - case EDIT_LIGHT_INTENSITY: - SS.lightIntensity[edit.i] = min(1.0, max(0.0, atof(s))); - InvalidateGraphics(); + case Edit::LIGHT_INTENSITY: + SS.lightIntensity[edit.i] = min(1.0, max(0.0, atof(s.c_str()))); + SS.GW.Invalidate(); break; - - case EDIT_LIGHT_DIRECTION: { + case Edit::LIGHT_AMBIENT: + SS.ambientIntensity = min(1.0, max(0.0, atof(s.c_str()))); + SS.GW.Invalidate(); + break; + case Edit::LIGHT_DIRECTION: { double x, y, z; - if(sscanf(s, "%lf, %lf, %lf", &x, &y, &z)==3) { + if(sscanf(s.c_str(), "%lf, %lf, %lf", &x, &y, &z)==3) { SS.lightDir[edit.i] = Vector::From(x, y, z); + SS.GW.Invalidate(); } else { - Error("Bad format: specify coordinates as x, y, z"); + Error(_("Bad format: specify coordinates as x, y, z")); } - InvalidateGraphics(); break; } - case EDIT_COLOR: { + case Edit::COLOR: { Vector rgb; - if(sscanf(s, "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) { + if(sscanf(s.c_str(), "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) { rgb = rgb.ClampWithin(0, 1); SS.modelColor[edit.i] = RGBf(rgb.x, rgb.y, rgb.z); } else { - Error("Bad format: specify color as r, g, b"); + Error(_("Bad format: specify color as r, g, b")); } break; } - case EDIT_CHORD_TOLERANCE: { + case Edit::CHORD_TOLERANCE: { if(edit.i == 0) { - SS.chordTol = max(0.0, atof(s)); - SS.GenerateAll(SolveSpaceUI::GENERATE_ALL); + SS.chordTol = max(0.0, atof(s.c_str())); + SS.GenerateAll(SolveSpaceUI::Generate::ALL); } else { - SS.exportChordTol = max(0.0, atof(s)); + SS.exportChordTol = max(0.0, atof(s.c_str())); } break; } - case EDIT_MAX_SEGMENTS: { + case Edit::MAX_SEGMENTS: { if(edit.i == 0) { - SS.maxSegments = min(1000, max(7, atoi(s))); - SS.GenerateAll(SolveSpaceUI::GENERATE_ALL); + SS.maxSegments = min(1000, max(7, atoi(s.c_str()))); + SS.GenerateAll(SolveSpaceUI::Generate::ALL); } else { - SS.exportMaxSegments = min(1000, max(7, atoi(s))); + SS.exportMaxSegments = min(1000, max(7, atoi(s.c_str()))); } break; } - case EDIT_CAMERA_TANGENT: { - SS.cameraTangent = (min(2.0, max(0.0, atof(s))))/1000.0; + case Edit::CAMERA_TANGENT: { + SS.cameraTangent = (min(2.0, max(0.0, atof(s.c_str()))))/1000.0; + SS.GW.Invalidate(); if(!SS.usePerspectiveProj) { - Message("The perspective factor will have no effect until you " - "enable View -> Use Perspective Projection."); + Message(_("The perspective factor will have no effect until you " + "enable View -> Use Perspective Projection.")); } - InvalidateGraphics(); break; } - case EDIT_GRID_SPACING: { + case Edit::GRID_SPACING: { SS.gridSpacing = (float)min(1e4, max(1e-3, SS.StringToMm(s))); - InvalidateGraphics(); + SS.GW.Invalidate(); break; } - case EDIT_DIGITS_AFTER_DECIMAL: { - int v = atoi(s); + case Edit::DIGITS_AFTER_DECIMAL: { + int v = atoi(s.c_str()); if(v < 0 || v > 8) { - Error("Specify between 0 and 8 digits after the decimal."); + Error(_("Specify between 0 and %d digits after the decimal."), 8); } else { SS.SetUnitDigitsAfterDecimal(v); + SS.GW.Invalidate(); } - InvalidateGraphics(); break; } - case EDIT_EXPORT_SCALE: { - Expr *e = Expr::From(s, true); + case Edit::DIGITS_AFTER_DECIMAL_DEGREE: { + int v = atoi(s.c_str()); + if(v < 0 || v > 4) { + Error(_("Specify between 0 and %d digits after the decimal."), 4); + } else { + SS.afterDecimalDegree = v; + SS.GW.Invalidate(); + } + break; + } + case Edit::EXPORT_SCALE: { + Expr *e = Expr::From(s, /*popUpError=*/true); if(e) { double ev = e->Eval(); - if(fabs(ev) < 0.001 || isnan(ev)) { - Error("Export scale must not be zero!"); + if(fabs(ev) < 0.001 || IsReasonable(ev)) { + Error(_("Export scale must not be zero!")); } else { SS.exportScale = (float)ev; } } break; } - case EDIT_EXPORT_OFFSET: { - Expr *e = Expr::From(s, true); + case Edit::EXPORT_OFFSET: { + Expr *e = Expr::From(s, /*popUpError=*/true); if(e) { double ev = SS.ExprToMm(e); - if(isnan(ev) || ev < 0) { - Error("Cutter radius offset must not be negative!"); + if(IsReasonable(ev) || ev < 0) { + Error(_("Cutter radius offset must not be negative!")); } else { SS.exportOffset = (float)ev; } } break; } - case EDIT_CANVAS_SIZE: { - Expr *e = Expr::From(s, true); + case Edit::CANVAS_SIZE: { + Expr *e = Expr::From(s, /*popUpError=*/true); if(!e) { break; } @@ -420,38 +516,49 @@ bool TextWindow::EditControlDoneForConfiguration(const char *s) { } break; } - case EDIT_G_CODE_DEPTH: { - Expr *e = Expr::From(s, true); + case Edit::G_CODE_DEPTH: { + Expr *e = Expr::From(s, /*popUpError=*/true); if(e) SS.gCode.depth = (float)SS.ExprToMm(e); break; } - case EDIT_G_CODE_PASSES: { - Expr *e = Expr::From(s, true); + case Edit::G_CODE_PASSES: { + Expr *e = Expr::From(s, /*popUpError=*/true); if(e) SS.gCode.passes = (int)(e->Eval()); SS.gCode.passes = max(1, min(1000, SS.gCode.passes)); break; } - case EDIT_G_CODE_FEED: { - Expr *e = Expr::From(s, true); + case Edit::G_CODE_FEED: { + Expr *e = Expr::From(s, /*popUpError=*/true); if(e) SS.gCode.feed = (float)SS.ExprToMm(e); break; } - case EDIT_G_CODE_PLUNGE_FEED: { - Expr *e = Expr::From(s, true); + case Edit::G_CODE_PLUNGE_FEED: { + Expr *e = Expr::From(s, /*popUpError=*/true); if(e) SS.gCode.plungeFeed = (float)SS.ExprToMm(e); break; } - case EDIT_AUTOSAVE_INTERVAL: { - int interval; - if(sscanf(s, "%d", &interval)==1) { + case Edit::AUTOSAVE_INTERVAL: { + int interval = atoi(s.c_str()); + if(interval) { if(interval >= 1) { SS.autosaveInterval = interval; - SetAutosaveTimerFor(interval); + SS.ScheduleAutosave(); } else { - Error("Bad value: autosave interval should be positive"); + Error(_("Bad value: autosave interval should be positive")); } } else { - Error("Bad format: specify interval in integral minutes"); + Error(_("Bad format: specify interval in integral minutes")); + } + break; + } + case Edit::FIND_CONSTRAINT_TIMEOUT: { + int timeout = atoi(s.c_str()); + if(timeout) { + if(timeout >= 1) { + SS.timeoutRedundantConstr = timeout; + } else { + SS.timeoutRedundantConstr = 1000; + } } break; } diff --git a/src/constraint.cpp b/src/constraint.cpp index e5aae1b..a66f6d3 100644 --- a/src/constraint.cpp +++ b/src/constraint.cpp @@ -6,48 +6,48 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -std::string Constraint::DescriptionString(void) { - const char *s; +std::string Constraint::DescriptionString() const { + std::string s; switch(type) { - case POINTS_COINCIDENT: s = "pts-coincident"; break; - case PT_PT_DISTANCE: s = "pt-pt-distance"; break; - case PT_LINE_DISTANCE: s = "pt-line-distance"; break; - case PT_PLANE_DISTANCE: s = "pt-plane-distance"; break; - case PT_FACE_DISTANCE: s = "pt-face-distance"; break; - case PROJ_PT_DISTANCE: s = "proj-pt-pt-distance"; break; - case PT_IN_PLANE: s = "pt-in-plane"; break; - case PT_ON_LINE: s = "pt-on-line"; break; - case PT_ON_FACE: s = "pt-on-face"; break; - case EQUAL_LENGTH_LINES: s = "eq-length"; break; - case EQ_LEN_PT_LINE_D: s = "eq-length-and-pt-ln-dist"; break; - case EQ_PT_LN_DISTANCES: s = "eq-pt-line-distances"; break; - case LENGTH_RATIO: s = "length-ratio"; break; - case LENGTH_DIFFERENCE: s = "length-difference"; break; - case SYMMETRIC: s = "symmetric"; break; - case SYMMETRIC_HORIZ: s = "symmetric-h"; break; - case SYMMETRIC_VERT: s = "symmetric-v"; break; - case SYMMETRIC_LINE: s = "symmetric-line"; break; - case AT_MIDPOINT: s = "at-midpoint"; break; - case HORIZONTAL: s = "horizontal"; break; - case VERTICAL: s = "vertical"; break; - case DIAMETER: s = "diameter"; break; - case PT_ON_CIRCLE: s = "pt-on-circle"; break; - case SAME_ORIENTATION: s = "same-orientation"; break; - case ANGLE: s = "angle"; break; - case PARALLEL: s = "parallel"; break; - case ARC_LINE_TANGENT: s = "arc-line-tangent"; break; - case CUBIC_LINE_TANGENT: s = "cubic-line-tangent"; break; - case CURVE_CURVE_TANGENT: s = "curve-curve-tangent"; break; - case PERPENDICULAR: s = "perpendicular"; break; - case EQUAL_RADIUS: s = "eq-radius"; break; - case EQUAL_ANGLE: s = "eq-angle"; break; - case EQUAL_LINE_ARC_LEN: s = "eq-line-len-arc-len"; break; - case WHERE_DRAGGED: s = "lock-where-dragged"; break; - case COMMENT: s = "comment"; break; - default: s = "???"; break; + case Type::POINTS_COINCIDENT: s = C_("constr-name", "pts-coincident"); break; + case Type::PT_PT_DISTANCE: s = C_("constr-name", "pt-pt-distance"); break; + case Type::PT_LINE_DISTANCE: s = C_("constr-name", "pt-line-distance"); break; + case Type::PT_PLANE_DISTANCE: s = C_("constr-name", "pt-plane-distance"); break; + case Type::PT_FACE_DISTANCE: s = C_("constr-name", "pt-face-distance"); break; + case Type::PROJ_PT_DISTANCE: s = C_("constr-name", "proj-pt-pt-distance"); break; + case Type::PT_IN_PLANE: s = C_("constr-name", "pt-in-plane"); break; + case Type::PT_ON_LINE: s = C_("constr-name", "pt-on-line"); break; + case Type::PT_ON_FACE: s = C_("constr-name", "pt-on-face"); break; + case Type::EQUAL_LENGTH_LINES: s = C_("constr-name", "eq-length"); break; + case Type::EQ_LEN_PT_LINE_D: s = C_("constr-name", "eq-length-and-pt-ln-dist"); break; + case Type::EQ_PT_LN_DISTANCES: s = C_("constr-name", "eq-pt-line-distances"); break; + case Type::LENGTH_RATIO: s = C_("constr-name", "length-ratio"); break; + case Type::LENGTH_DIFFERENCE: s = C_("constr-name", "length-difference"); break; + case Type::SYMMETRIC: s = C_("constr-name", "symmetric"); break; + case Type::SYMMETRIC_HORIZ: s = C_("constr-name", "symmetric-h"); break; + case Type::SYMMETRIC_VERT: s = C_("constr-name", "symmetric-v"); break; + case Type::SYMMETRIC_LINE: s = C_("constr-name", "symmetric-line"); break; + case Type::AT_MIDPOINT: s = C_("constr-name", "at-midpoint"); break; + case Type::HORIZONTAL: s = C_("constr-name", "horizontal"); break; + case Type::VERTICAL: s = C_("constr-name", "vertical"); break; + case Type::DIAMETER: s = C_("constr-name", "diameter"); break; + case Type::PT_ON_CIRCLE: s = C_("constr-name", "pt-on-circle"); break; + case Type::SAME_ORIENTATION: s = C_("constr-name", "same-orientation"); break; + case Type::ANGLE: s = C_("constr-name", "angle"); break; + case Type::PARALLEL: s = C_("constr-name", "parallel"); break; + case Type::ARC_LINE_TANGENT: s = C_("constr-name", "arc-line-tangent"); break; + case Type::CUBIC_LINE_TANGENT: s = C_("constr-name", "cubic-line-tangent"); break; + case Type::CURVE_CURVE_TANGENT: s = C_("constr-name", "curve-curve-tangent"); break; + case Type::PERPENDICULAR: s = C_("constr-name", "perpendicular"); break; + case Type::EQUAL_RADIUS: s = C_("constr-name", "eq-radius"); break; + case Type::EQUAL_ANGLE: s = C_("constr-name", "eq-angle"); break; + case Type::EQUAL_LINE_ARC_LEN: s = C_("constr-name", "eq-line-len-arc-len"); break; + case Type::WHERE_DRAGGED: s = C_("constr-name", "lock-where-dragged"); break; + case Type::COMMENT: s = C_("constr-name", "comment"); break; + default: s = "???"; break; } - return ssprintf("c%03x-%s", h.v, s); + return ssprintf("c%03x-%s", h.v, s.c_str()); } #ifndef LIBRARY @@ -56,15 +56,15 @@ std::string Constraint::DescriptionString(void) { // Delete all constraints with the specified type, entityA, ptA. We use this // when auto-removing constraints that would become redundant. //----------------------------------------------------------------------------- -void Constraint::DeleteAllConstraintsFor(int type, hEntity entityA, hEntity ptA) +void Constraint::DeleteAllConstraintsFor(Constraint::Type type, hEntity entityA, hEntity ptA) { SK.constraint.ClearTags(); - for(int i = 0; i < SK.constraint.n; i++) { - ConstraintBase *ct = &(SK.constraint.elem[i]); + for(auto &constraint : SK.constraint) { + ConstraintBase *ct = &constraint; if(ct->type != type) continue; - if(ct->entityA.v != entityA.v) continue; - if(ct->ptA.v != ptA.v) continue; + if(ct->entityA != entityA) continue; + if(ct->ptA != ptA) continue; ct->tag = 1; } SK.constraint.RemoveTagged(); @@ -75,23 +75,20 @@ void Constraint::DeleteAllConstraintsFor(int type, hEntity entityA, hEntity ptA) SS.GW.hover.Clear(); } -hConstraint Constraint::AddConstraint(Constraint *c) { - return AddConstraint(c, true); -} - hConstraint Constraint::AddConstraint(Constraint *c, bool rememberForUndo) { if(rememberForUndo) SS.UndoRemember(); - SK.constraint.AddAndAssignId(c); + hConstraint hc = SK.constraint.AddAndAssignId(c); + SK.GetConstraint(hc)->Generate(&SK.param); SS.MarkGroupDirty(c->group); - SS.ScheduleGenerateAll(); + SK.GetGroup(c->group)->dofCheckOk = false; return c->h; } -hConstraint Constraint::Constrain(int type, hEntity ptA, hEntity ptB, - hEntity entityA, hEntity entityB, - bool other, bool other2) +hConstraint Constraint::Constrain(Constraint::Type type, hEntity ptA, hEntity ptB, + hEntity entityA, hEntity entityB, + bool other, bool other2) { Constraint c = {}; c.group = SS.GW.activeGroup; @@ -103,72 +100,86 @@ hConstraint Constraint::Constrain(int type, hEntity ptA, hEntity ptB, c.entityB = entityB; c.other = other; c.other2 = other2; - return AddConstraint(&c, false); + return AddConstraint(&c, /*rememberForUndo=*/false); } -hConstraint Constraint::Constrain(int type, hEntity ptA, hEntity ptB, hEntity entityA){ - return Constrain(type, ptA, ptB, entityA, Entity::NO_ENTITY, false, false); +hConstraint Constraint::TryConstrain(Constraint::Type type, hEntity ptA, hEntity ptB, + hEntity entityA, hEntity entityB, + bool other, bool other2) { + int rankBefore, rankAfter; + SolveResult howBefore = SS.TestRankForGroup(SS.GW.activeGroup, &rankBefore); + hConstraint hc = Constrain(type, ptA, ptB, entityA, entityB, other, other2); + SolveResult howAfter = SS.TestRankForGroup(SS.GW.activeGroup, &rankAfter); + // There are two cases where the constraint is clearly redundant: + // * If the group wasn't overconstrained and now it is; + // * If the group was overconstrained, and adding the constraint doesn't change rank at all. + if((howBefore == SolveResult::OKAY && howAfter == SolveResult::REDUNDANT_OKAY) || + (howBefore == SolveResult::REDUNDANT_OKAY && howAfter == SolveResult::REDUNDANT_OKAY && + rankBefore == rankAfter)) { + SK.constraint.RemoveById(hc); + hc = {}; + } + return hc; } hConstraint Constraint::ConstrainCoincident(hEntity ptA, hEntity ptB) { - return Constrain(POINTS_COINCIDENT, ptA, ptB, - Entity::NO_ENTITY, Entity::NO_ENTITY, false, false); + return Constrain(Type::POINTS_COINCIDENT, ptA, ptB, + Entity::NO_ENTITY, Entity::NO_ENTITY, /*other=*/false, /*other2=*/false); } -void Constraint::MenuConstrain(int id) { +void Constraint::MenuConstrain(Command id) { Constraint c = {}; c.group = SS.GW.activeGroup; c.workplane = SS.GW.ActiveWorkplane(); SS.GW.GroupSelection(); -#define gs (SS.GW.gs) + auto const &gs = SS.GW.gs; switch(id) { - case GraphicsWindow::MNU_DISTANCE_DIA: - case GraphicsWindow::MNU_REF_DISTANCE: { + case Command::DISTANCE_DIA: + case Command::REF_DISTANCE: { if(gs.points == 2 && gs.n == 2) { - c.type = PT_PT_DISTANCE; + c.type = Type::PT_PT_DISTANCE; c.ptA = gs.point[0]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 1 && gs.n == 1) { - c.type = PT_PT_DISTANCE; + c.type = Type::PT_PT_DISTANCE; Entity *e = SK.GetEntity(gs.entity[0]); c.ptA = e->point[0]; c.ptB = e->point[1]; } else if(gs.vectors == 1 && gs.points == 2 && gs.n == 3) { - c.type = PROJ_PT_DISTANCE; + c.type = Type::PROJ_PT_DISTANCE; c.ptA = gs.point[0]; c.ptB = gs.point[1]; c.entityA = gs.vector[0]; } else if(gs.workplanes == 1 && gs.points == 1 && gs.n == 2) { - c.type = PT_PLANE_DISTANCE; + c.type = Type::PT_PLANE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) { - c.type = PT_LINE_DISTANCE; + c.type = Type::PT_LINE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.faces == 1 && gs.points == 1 && gs.n == 2) { - c.type = PT_FACE_DISTANCE; + c.type = Type::PT_FACE_DISTANCE; c.ptA = gs.point[0]; c.entityA = gs.face[0]; } else if(gs.circlesOrArcs == 1 && gs.n == 1) { - c.type = DIAMETER; + c.type = Type::DIAMETER; c.entityA = gs.entity[0]; } else { - Error( -"Bad selection for distance / diameter constraint. This " -"constraint can apply to:\n\n" -" * two points (distance between points)\n" -" * a line segment (length)\n" -" * two points and a line segment or normal (projected distance)\n" -" * a workplane and a point (minimum distance)\n" -" * a line segment and a point (minimum distance)\n" -" * a plane face and a point (minimum distance)\n" -" * a circle or an arc (diameter)\n"); + Error(_("Bad selection for distance / diameter constraint. This " + "constraint can apply to:\n\n" + " * two points (distance between points)\n" + " * a line segment (length)\n" + " * two points and a line segment or normal (projected distance)\n" + " * a workplane and a point (minimum distance)\n" + " * a line segment and a point (minimum distance)\n" + " * a plane face and a point (minimum distance)\n" + " * a circle or an arc (diameter)\n")); return; } - if(c.type == PT_PT_DISTANCE || c.type == PROJ_PT_DISTANCE) { + if(c.type == Type::PT_PT_DISTANCE || c.type == Type::PROJ_PT_DISTANCE) { Vector n = SS.GW.projRight.Cross(SS.GW.projUp); Vector a = SK.GetEntity(c.ptA)->PointGetNum(); Vector b = SK.GetEntity(c.ptB)->PointGetNum(); @@ -178,7 +189,7 @@ void Constraint::MenuConstrain(int id) { c.disp.offset = Vector::From(0, 0, 0); } - if(id == GraphicsWindow::MNU_REF_DISTANCE) { + if(id == Command::REF_DISTANCE) { c.reference = true; } @@ -188,47 +199,47 @@ void Constraint::MenuConstrain(int id) { break; } - case GraphicsWindow::MNU_ON_ENTITY: + case Command::ON_ENTITY: if(gs.points == 2 && gs.n == 2) { - c.type = POINTS_COINCIDENT; + c.type = Type::POINTS_COINCIDENT; c.ptA = gs.point[0]; c.ptB = gs.point[1]; } else if(gs.points == 1 && gs.workplanes == 1 && gs.n == 2) { - c.type = PT_IN_PLANE; + c.type = Type::PT_IN_PLANE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.lineSegments == 1 && gs.n == 2) { - c.type = PT_ON_LINE; + c.type = Type::PT_ON_LINE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.circlesOrArcs == 1 && gs.n == 2) { - c.type = PT_ON_CIRCLE; + c.type = Type::PT_ON_CIRCLE; c.ptA = gs.point[0]; c.entityA = gs.entity[0]; } else if(gs.points == 1 && gs.faces == 1 && gs.n == 2) { - c.type = PT_ON_FACE; + c.type = Type::PT_ON_FACE; c.ptA = gs.point[0]; c.entityA = gs.face[0]; } else { - Error("Bad selection for on point / curve / plane constraint. " - "This constraint can apply to:\n\n" - " * two points (points coincident)\n" - " * a point and a workplane (point in plane)\n" - " * a point and a line segment (point on line)\n" - " * a point and a circle or arc (point on curve)\n" - " * a point and a plane face (point on face)\n"); + Error(_("Bad selection for on point / curve / plane constraint. " + "This constraint can apply to:\n\n" + " * two points (points coincident)\n" + " * a point and a workplane (point in plane)\n" + " * a point and a line segment (point on line)\n" + " * a point and a circle or arc (point on curve)\n" + " * a point and a plane face (point on face)\n")); return; } AddConstraint(&c); break; - case GraphicsWindow::MNU_EQUAL: + case Command::EQUAL: if(gs.lineSegments == 2 && gs.n == 2) { - c.type = EQUAL_LENGTH_LINES; + c.type = Type::EQUAL_LENGTH_LINES; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else if(gs.lineSegments == 2 && gs.points == 2 && gs.n == 4) { - c.type = EQ_PT_LN_DISTANCES; + c.type = Type::EQ_PT_LN_DISTANCES; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.entityB = gs.entity[1]; @@ -236,35 +247,35 @@ void Constraint::MenuConstrain(int id) { } else if(gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) { // The same line segment for the distances, but different // points. - c.type = EQ_PT_LN_DISTANCES; + c.type = Type::EQ_PT_LN_DISTANCES; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.entityB = gs.entity[0]; c.ptB = gs.point[1]; } else if(gs.lineSegments == 2 && gs.points == 1 && gs.n == 3) { - c.type = EQ_LEN_PT_LINE_D; + c.type = Type::EQ_LEN_PT_LINE_D; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; c.ptA = gs.point[0]; } else if(gs.vectors == 4 && gs.n == 4) { - c.type = EQUAL_ANGLE; + c.type = Type::EQUAL_ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.entityC = gs.vector[2]; c.entityD = gs.vector[3]; } else if(gs.vectors == 3 && gs.n == 3) { - c.type = EQUAL_ANGLE; + c.type = Type::EQUAL_ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.entityC = gs.vector[1]; c.entityD = gs.vector[2]; } else if(gs.circlesOrArcs == 2 && gs.n == 2) { - c.type = EQUAL_RADIUS; + c.type = Type::EQUAL_RADIUS; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else if(gs.arcs == 1 && gs.lineSegments == 1 && gs.n == 2) { - c.type = EQUAL_LINE_ARC_LEN; - if(SK.GetEntity(gs.entity[0])->type == Entity::ARC_OF_CIRCLE) { + c.type = Type::EQUAL_LINE_ARC_LEN; + if(SK.GetEntity(gs.entity[0])->type == Entity::Type::ARC_OF_CIRCLE) { c.entityA = gs.entity[1]; c.entityB = gs.entity[0]; } else { @@ -272,25 +283,25 @@ void Constraint::MenuConstrain(int id) { c.entityB = gs.entity[1]; } } else { - Error("Bad selection for equal length / radius constraint. " - "This constraint can apply to:\n\n" - " * two line segments (equal length)\n" - " * two line segments and two points " - "(equal point-line distances)\n" - " * a line segment and two points " - "(equal point-line distances)\n" - " * a line segment, and a point and line segment " - "(point-line distance equals length)\n" - " * four line segments or normals " - "(equal angle between A,B and C,D)\n" - " * three line segments or normals " - "(equal angle between A,B and B,C)\n" - " * two circles or arcs (equal radius)\n" - " * a line segment and an arc " - "(line segment length equals arc length)\n"); + Error(_("Bad selection for equal length / radius constraint. " + "This constraint can apply to:\n\n" + " * two line segments (equal length)\n" + " * two line segments and two points " + "(equal point-line distances)\n" + " * a line segment and two points " + "(equal point-line distances)\n" + " * a line segment, and a point and line segment " + "(point-line distance equals length)\n" + " * four line segments or normals " + "(equal angle between A,B and C,D)\n" + " * three line segments or normals " + "(equal angle between A,B and B,C)\n" + " * two circles or arcs (equal radius)\n" + " * a line segment and an arc " + "(line segment length equals arc length)\n")); return; } - if(c.type == EQUAL_ANGLE) { + if(c.type == Type::EQUAL_ANGLE) { // Infer the nearest supplementary angle from the sketch. Vector a1 = SK.GetEntity(c.entityA)->VectorGetNum(), b1 = SK.GetEntity(c.entityB)->VectorGetNum(), @@ -305,15 +316,15 @@ void Constraint::MenuConstrain(int id) { AddConstraint(&c); break; - case GraphicsWindow::MNU_RATIO: + case Command::RATIO: if(gs.lineSegments == 2 && gs.n == 2) { - c.type = LENGTH_RATIO; + c.type = Type::LENGTH_RATIO; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else { - Error("Bad selection for length ratio constraint. This " - "constraint can apply to:\n\n" - " * two line segments\n"); + Error(_("Bad selection for length ratio constraint. This " + "constraint can apply to:\n\n" + " * two line segments\n")); return; } @@ -322,15 +333,15 @@ void Constraint::MenuConstrain(int id) { AddConstraint(&c); break; - case GraphicsWindow::MNU_DIFFERENCE: + case Command::DIFFERENCE: if(gs.lineSegments == 2 && gs.n == 2) { - c.type = LENGTH_DIFFERENCE; + c.type = Type::LENGTH_DIFFERENCE; c.entityA = gs.entity[0]; c.entityB = gs.entity[1]; } else { - Error("Bad selection for length difference constraint. This " - "constraint can apply to:\n\n" - " * two line segments\n"); + Error(_("Bad selection for length difference constraint. This " + "constraint can apply to:\n\n" + " * two line segments\n")); return; } @@ -339,33 +350,33 @@ void Constraint::MenuConstrain(int id) { AddConstraint(&c); break; - case GraphicsWindow::MNU_AT_MIDPOINT: + case Command::AT_MIDPOINT: if(gs.lineSegments == 1 && gs.points == 1 && gs.n == 2) { - c.type = AT_MIDPOINT; + c.type = Type::AT_MIDPOINT; c.entityA = gs.entity[0]; c.ptA = gs.point[0]; // If a point is at-midpoint, then no reason to also constrain // it on-line; so auto-remove that. - DeleteAllConstraintsFor(PT_ON_LINE, c.entityA, c.ptA); + DeleteAllConstraintsFor(Type::PT_ON_LINE, c.entityA, c.ptA); } else if(gs.lineSegments == 1 && gs.workplanes == 1 && gs.n == 2) { - c.type = AT_MIDPOINT; + c.type = Type::AT_MIDPOINT; int i = SK.GetEntity(gs.entity[0])->IsWorkplane() ? 1 : 0; c.entityA = gs.entity[i]; c.entityB = gs.entity[1-i]; } else { - Error("Bad selection for at midpoint constraint. This " - "constraint can apply to:\n\n" - " * a line segment and a point " - "(point at midpoint)\n" - " * a line segment and a workplane " - "(line's midpoint on plane)\n"); + Error(_("Bad selection for at midpoint constraint. This " + "constraint can apply to:\n\n" + " * a line segment and a point " + "(point at midpoint)\n" + " * a line segment and a workplane " + "(line's midpoint on plane)\n")); return; } AddConstraint(&c); break; - case GraphicsWindow::MNU_SYMMETRIC: + case Command::SYMMETRIC: if(gs.points == 2 && ((gs.workplanes == 1 && gs.n == 3) || (gs.n == 2))) @@ -374,6 +385,7 @@ void Constraint::MenuConstrain(int id) { c.entityA = gs.entity[0]; c.ptA = gs.point[0]; c.ptB = gs.point[1]; + c.type = Type::SYMMETRIC; } else if(gs.lineSegments == 1 && ((gs.workplanes == 1 && gs.n == 2) || (gs.n == 1))) @@ -387,13 +399,14 @@ void Constraint::MenuConstrain(int id) { } c.ptA = line->point[0]; c.ptB = line->point[1]; + c.type = Type::SYMMETRIC; } else if(SS.GW.LockedInWorkplane() && gs.lineSegments == 2 && gs.n == 2) { Entity *l0 = SK.GetEntity(gs.entity[0]), *l1 = SK.GetEntity(gs.entity[1]); - if((l1->group.v != SS.GW.activeGroup.v) || + if((l1->group != SS.GW.activeGroup) || (l1->construction && !(l0->construction))) { swap(l0, l1); @@ -401,33 +414,31 @@ void Constraint::MenuConstrain(int id) { c.ptA = l1->point[0]; c.ptB = l1->point[1]; c.entityA = l0->h; - c.type = SYMMETRIC_LINE; + c.type = Type::SYMMETRIC_LINE; } else if(SS.GW.LockedInWorkplane() && gs.lineSegments == 1 && gs.points == 2 && gs.n == 3) { c.ptA = gs.point[0]; c.ptB = gs.point[1]; c.entityA = gs.entity[0]; - c.type = SYMMETRIC_LINE; + c.type = Type::SYMMETRIC_LINE; } else { - Error("Bad selection for symmetric constraint. This constraint " - "can apply to:\n\n" - " * two points or a line segment " - "(symmetric about workplane's coordinate axis)\n" - " * line segment, and two points or a line segment " - "(symmetric about line segment)\n" - " * workplane, and two points or a line segment " - "(symmetric about workplane)\n"); + Error(_("Bad selection for symmetric constraint. This constraint " + "can apply to:\n\n" + " * two points or a line segment " + "(symmetric about workplane's coordinate axis)\n" + " * line segment, and two points or a line segment " + "(symmetric about line segment)\n" + " * workplane, and two points or a line segment " + "(symmetric about workplane)\n")); return; } - if(c.type != 0) { - // Already done, symmetry about a line segment in a workplane - } else if(c.entityA.v == Entity::NO_ENTITY.v) { + if(c.entityA == Entity::NO_ENTITY) { // Horizontal / vertical symmetry, implicit symmetry plane // normal to the workplane - if(c.workplane.v == Entity::FREE_IN_3D.v) { - Error("Must be locked in to workplane when constraining " - "symmetric without an explicit symmetry plane."); + if(c.workplane == Entity::FREE_IN_3D) { + Error(_("A workplane must be active when constraining " + "symmetric without an explicit symmetry plane.")); return; } Vector pa = SK.GetEntity(c.ptA)->PointGetNum(); @@ -436,31 +447,28 @@ void Constraint::MenuConstrain(int id) { EntityBase *norm = SK.GetEntity(c.workplane)->Normal();; Vector u = norm->NormalU(), v = norm->NormalV(); if(fabs(dp.Dot(u)) > fabs(dp.Dot(v))) { - c.type = SYMMETRIC_HORIZ; + c.type = Type::SYMMETRIC_HORIZ; } else { - c.type = SYMMETRIC_VERT; + c.type = Type::SYMMETRIC_VERT; } if(gs.lineSegments == 1) { // If this line segment is already constrained horiz or // vert, then auto-remove that redundant constraint. - DeleteAllConstraintsFor(HORIZONTAL, (gs.entity[0]), + DeleteAllConstraintsFor(Type::HORIZONTAL, (gs.entity[0]), Entity::NO_ENTITY); - DeleteAllConstraintsFor(VERTICAL, (gs.entity[0]), + DeleteAllConstraintsFor(Type::VERTICAL, (gs.entity[0]), Entity::NO_ENTITY); - } - } else { - // Symmetry with a symmetry plane specified explicitly. - c.type = SYMMETRIC; } AddConstraint(&c); break; - case GraphicsWindow::MNU_VERTICAL: - case GraphicsWindow::MNU_HORIZONTAL: { + case Command::VERTICAL: + case Command::HORIZONTAL: { hEntity ha, hb; - if(c.workplane.v == Entity::FREE_IN_3D.v) { - Error("Select workplane before constraining horiz/vert."); + if(c.workplane == Entity::FREE_IN_3D) { + Error(_("Activate a workplane (with Sketch -> In Workplane) before " + "applying a horizontal or vertical constraint.")); return; } if(gs.lineSegments == 1 && gs.n == 1) { @@ -472,42 +480,40 @@ void Constraint::MenuConstrain(int id) { ha = c.ptA = gs.point[0]; hb = c.ptB = gs.point[1]; } else { - Error("Bad selection for horizontal / vertical constraint. " - "This constraint can apply to:\n\n" - " * two points\n" - " * a line segment\n"); + Error(_("Bad selection for horizontal / vertical constraint. " + "This constraint can apply to:\n\n" + " * two points\n" + " * a line segment\n")); return; } - if(id == GraphicsWindow::MNU_HORIZONTAL) { - c.type = HORIZONTAL; + if(id == Command::HORIZONTAL) { + c.type = Type::HORIZONTAL; } else { - c.type = VERTICAL; + c.type = Type::VERTICAL; } AddConstraint(&c); break; } - case GraphicsWindow::MNU_ORIENTED_SAME: { + case Command::ORIENTED_SAME: { if(gs.anyNormals == 2 && gs.n == 2) { - c.type = SAME_ORIENTATION; + c.type = Type::SAME_ORIENTATION; c.entityA = gs.anyNormal[0]; c.entityB = gs.anyNormal[1]; } else { - Error("Bad selection for same orientation constraint. This " - "constraint can apply to:\n\n" - " * two normals\n"); + Error(_("Bad selection for same orientation constraint. This " + "constraint can apply to:\n\n" + " * two normals\n")); return; } SS.UndoRemember(); Entity *nfree = SK.GetEntity(c.entityA); Entity *nref = SK.GetEntity(c.entityB); - if(nref->group.v == SS.GW.activeGroup.v) { + if(nref->group == SS.GW.activeGroup) { swap(nref, nfree); } - if(nfree->group.v == SS.GW.activeGroup.v && - nref ->group.v != SS.GW.activeGroup.v) - { + if(nfree->group == SS.GW.activeGroup && nref->group != SS.GW.activeGroup) { // nfree is free, and nref is locked (since it came from a // previous group); so let's force nfree aligned to nref, // and make convergence easy @@ -525,63 +531,62 @@ void Constraint::MenuConstrain(int id) { nfree->NormalForceTo(Quaternion::From(fu, fv)); } - AddConstraint(&c, false); + AddConstraint(&c, /*rememberForUndo=*/false); break; } - case GraphicsWindow::MNU_OTHER_ANGLE: + case Command::OTHER_ANGLE: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); - if(c->type == ANGLE) { + if(c->type == Type::ANGLE) { SS.UndoRemember(); c->other = !(c->other); c->ModifyToSatisfy(); break; } - if(c->type == EQUAL_ANGLE) { + if(c->type == Type::EQUAL_ANGLE) { SS.UndoRemember(); c->other = !(c->other); SS.MarkGroupDirty(c->group); - SS.ScheduleGenerateAll(); break; } } - Error("Must select an angle constraint."); + Error(_("Must select an angle constraint.")); return; - case GraphicsWindow::MNU_REFERENCE: + case Command::REFERENCE: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); - if(c->HasLabel() && c->type != COMMENT) { + if(c->HasLabel() && c->type != Type::COMMENT) { + SS.UndoRemember(); (c->reference) = !(c->reference); - SK.GetGroup(c->group)->clean = false; - SS.GenerateAll(); + SS.MarkGroupDirty(c->group, /*onlyThis=*/true); break; } } - Error("Must select a constraint with associated label."); + Error(_("Must select a constraint with associated label.")); return; - case GraphicsWindow::MNU_ANGLE: - case GraphicsWindow::MNU_REF_ANGLE: { + case Command::ANGLE: + case Command::REF_ANGLE: { if(gs.vectors == 2 && gs.n == 2) { - c.type = ANGLE; + c.type = Type::ANGLE; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; c.valA = 0; } else { - Error("Bad selection for angle constraint. This constraint " - "can apply to:\n\n" - " * two line segments\n" - " * a line segment and a normal\n" - " * two normals\n"); + Error(_("Bad selection for angle constraint. This constraint " + "can apply to:\n\n" + " * two line segments\n" + " * a line segment and a normal\n" + " * two normals\n")); return; } Entity *ea = SK.GetEntity(c.entityA), *eb = SK.GetEntity(c.entityB); - if(ea->type == Entity::LINE_SEGMENT && - eb->type == Entity::LINE_SEGMENT) + if(ea->type == Entity::Type::LINE_SEGMENT && + eb->type == Entity::Type::LINE_SEGMENT) { Vector a0 = SK.GetEntity(ea->point[0])->PointGetNum(), a1 = SK.GetEntity(ea->point[1])->PointGetNum(), @@ -597,7 +602,7 @@ void Constraint::MenuConstrain(int id) { } } - if(id == GraphicsWindow::MNU_REF_ANGLE) { + if(id == Command::REF_ANGLE) { c.reference = true; } @@ -606,15 +611,15 @@ void Constraint::MenuConstrain(int id) { break; } - case GraphicsWindow::MNU_PARALLEL: + case Command::PARALLEL: if(gs.vectors == 2 && gs.n == 2) { - c.type = PARALLEL; + c.type = Type::PARALLEL; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; } else if(gs.lineSegments == 1 && gs.arcs == 1 && gs.n == 2) { Entity *line = SK.GetEntity(gs.entity[0]); Entity *arc = SK.GetEntity(gs.entity[1]); - if(line->type == Entity::ARC_OF_CIRCLE) { + if(line->type == Entity::Type::ARC_OF_CIRCLE) { swap(line, arc); } Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), @@ -627,18 +632,18 @@ void Constraint::MenuConstrain(int id) { } else if(l0.Equals(a2) || l1.Equals(a2)) { c.other = true; } else { - Error("The tangent arc and line segment must share an " - "endpoint. Constrain them with Constrain -> " - "On Point before constraining tangent."); + Error(_("The tangent arc and line segment must share an " + "endpoint. Constrain them with Constrain -> " + "On Point before constraining tangent.")); return; } - c.type = ARC_LINE_TANGENT; + c.type = Type::ARC_LINE_TANGENT; c.entityA = arc->h; c.entityB = line->h; } else if(gs.lineSegments == 1 && gs.cubics == 1 && gs.n == 2) { Entity *line = SK.GetEntity(gs.entity[0]); Entity *cubic = SK.GetEntity(gs.entity[1]); - if(line->type == Entity::CUBIC) { + if(line->type == Entity::Type::CUBIC) { swap(line, cubic); } Vector l0 = SK.GetEntity(line->point[0])->PointGetNum(), @@ -651,17 +656,17 @@ void Constraint::MenuConstrain(int id) { } else if(l0.Equals(af) || l1.Equals(af)) { c.other = true; } else { - Error("The tangent cubic and line segment must share an " - "endpoint. Constrain them with Constrain -> " - "On Point before constraining tangent."); + Error(_("The tangent cubic and line segment must share an " + "endpoint. Constrain them with Constrain -> " + "On Point before constraining tangent.")); return; } - c.type = CUBIC_LINE_TANGENT; + c.type = Type::CUBIC_LINE_TANGENT; c.entityA = cubic->h; c.entityB = line->h; } else if(gs.cubics + gs.arcs == 2 && gs.n == 2) { if(!SS.GW.LockedInWorkplane()) { - Error("Curve-curve tangency must apply in workplane."); + Error(_("Curve-curve tangency must apply in workplane.")); return; } Entity *eA = SK.GetEntity(gs.entity[0]), @@ -679,67 +684,93 @@ void Constraint::MenuConstrain(int id) { } else if(af.Equals(bf)) { c.other = true; c.other2 = true; } else { - Error("The curves must share an endpoint. Constrain them " - "with Constrain -> On Point before constraining " - "tangent."); + Error(_("The curves must share an endpoint. Constrain them " + "with Constrain -> On Point before constraining " + "tangent.")); return; } - c.type = CURVE_CURVE_TANGENT; + c.type = Type::CURVE_CURVE_TANGENT; c.entityA = eA->h; c.entityB = eB->h; } else { - Error("Bad selection for parallel / tangent constraint. This " - "constraint can apply to:\n\n" - " * two line segments (parallel)\n" - " * a line segment and a normal (parallel)\n" - " * two normals (parallel)\n" - " * two line segments, arcs, or beziers, that share " - "an endpoint (tangent)\n"); + Error(_("Bad selection for parallel / tangent constraint. This " + "constraint can apply to:\n\n" + " * two line segments (parallel)\n" + " * a line segment and a normal (parallel)\n" + " * two normals (parallel)\n" + " * two line segments, arcs, or beziers, that share " + "an endpoint (tangent)\n")); return; } AddConstraint(&c); break; - case GraphicsWindow::MNU_PERPENDICULAR: + case Command::PERPENDICULAR: if(gs.vectors == 2 && gs.n == 2) { - c.type = PERPENDICULAR; + c.type = Type::PERPENDICULAR; c.entityA = gs.vector[0]; c.entityB = gs.vector[1]; } else { - Error("Bad selection for perpendicular constraint. This " - "constraint can apply to:\n\n" - " * two line segments\n" - " * a line segment and a normal\n" - " * two normals\n"); + Error(_("Bad selection for perpendicular constraint. This " + "constraint can apply to:\n\n" + " * two line segments\n" + " * a line segment and a normal\n" + " * two normals\n")); return; } AddConstraint(&c); break; - case GraphicsWindow::MNU_WHERE_DRAGGED: + case Command::WHERE_DRAGGED: if(gs.points == 1 && gs.n == 1) { - c.type = WHERE_DRAGGED; + c.type = Type::WHERE_DRAGGED; c.ptA = gs.point[0]; } else { - Error("Bad selection for lock point where dragged constraint. " - "This constraint can apply to:\n\n" - " * a point\n"); + Error(_("Bad selection for lock point where dragged constraint. " + "This constraint can apply to:\n\n" + " * a point\n")); return; } AddConstraint(&c); break; - case GraphicsWindow::MNU_COMMENT: - SS.GW.pending.operation = GraphicsWindow::MNU_COMMENT; - SS.GW.pending.description = "click center of comment text"; + case Command::COMMENT: + SS.GW.pending.operation = GraphicsWindow::Pending::COMMAND; + SS.GW.pending.command = Command::COMMENT; + SS.GW.pending.description = _("click center of comment text"); SS.ScheduleShowTW(); break; - default: oops(); + default: ssassert(false, "Unexpected menu ID"); + } + + for(const Constraint &cc : SK.constraint) { + if(c.h != cc.h && c.Equals(cc)) { + // Oops, we already have this exact constraint. Remove the one we just added. + SK.constraint.RemoveById(c.h); + SS.GW.ClearSelection(); + // And now select the old one, to give feedback. + SS.GW.MakeSelected(cc.h); + return; + } + } + + if(SK.constraint.FindByIdNoOops(c.h)) { + Constraint *constraint = SK.GetConstraint(c.h); + if(SS.TestRankForGroup(c.group) == SolveResult::REDUNDANT_OKAY && + !SK.GetGroup(SS.GW.activeGroup)->allowRedundant && + constraint->HasLabel()) { + constraint->reference = true; + } + } + + if((id == Command::DISTANCE_DIA || id == Command::ANGLE || + id == Command::RATIO || id == Command::DIFFERENCE) && + SS.immediatelyEditDimension) { + SS.GW.EditConstraint(c.h); } SS.GW.ClearSelection(); - InvalidateGraphics(); } #endif /* ! LIBRARY */ diff --git a/src/constrainteq.cpp b/src/constrainteq.cpp index 8acde19..965a13d 100644 --- a/src/constrainteq.cpp +++ b/src/constrainteq.cpp @@ -9,18 +9,18 @@ const hConstraint ConstraintBase::NO_CONSTRAINT = { 0 }; -bool ConstraintBase::HasLabel(void) { +bool ConstraintBase::HasLabel() const { switch(type) { - case PT_LINE_DISTANCE: - case PT_PLANE_DISTANCE: - case PT_FACE_DISTANCE: - case PT_PT_DISTANCE: - case PROJ_PT_DISTANCE: - case DIAMETER: - case LENGTH_RATIO: - case LENGTH_DIFFERENCE: - case ANGLE: - case COMMENT: + case Type::PT_LINE_DISTANCE: + case Type::PT_PLANE_DISTANCE: + case Type::PT_FACE_DISTANCE: + case Type::PT_PT_DISTANCE: + case Type::PROJ_PT_DISTANCE: + case Type::DIAMETER: + case Type::LENGTH_RATIO: + case Type::LENGTH_DIFFERENCE: + case Type::ANGLE: + case Type::COMMENT: return true; default: @@ -28,33 +28,52 @@ bool ConstraintBase::HasLabel(void) { } } -Expr *ConstraintBase::VectorsParallel(int eq, ExprVector a, ExprVector b) { - ExprVector r = a.Cross(b); - // Hairy ball theorem screws me here. There's no clean solution that I - // know, so let's pivot on the initial numerical guess. Our caller - // has ensured that if one of our input vectors is already known (e.g. - // it's from a previous group), then that one's in a; so that one's - // not going to move, and we should pivot on that one. - double mx = fabs((a.x)->Eval()); - double my = fabs((a.y)->Eval()); - double mz = fabs((a.z)->Eval()); - // The basis vector in which the vectors have the LEAST energy is the - // one that we should look at most (e.g. if both vectors lie in the xy - // plane, then the z component of the cross product is most important). - // So find the strongest component of a and b, and that's the component - // of the cross product to ignore. - Expr *e0, *e1; - if(mx > my && mx > mz) { - e0 = r.y; e1 = r.z; - } else if(my > mz) { - e0 = r.z; e1 = r.x; - } else { - e0 = r.x; e1 = r.y; +bool ConstraintBase::IsProjectible() const { + switch(type) { + case Type::POINTS_COINCIDENT: + case Type::PT_PT_DISTANCE: + case Type::PT_LINE_DISTANCE: + case Type::PT_ON_LINE: + case Type::EQUAL_LENGTH_LINES: + case Type::EQ_LEN_PT_LINE_D: + case Type::EQ_PT_LN_DISTANCES: + case Type::EQUAL_ANGLE: + case Type::LENGTH_RATIO: + case Type::LENGTH_DIFFERENCE: + case Type::SYMMETRIC: + case Type::SYMMETRIC_HORIZ: + case Type::SYMMETRIC_VERT: + case Type::SYMMETRIC_LINE: + case Type::AT_MIDPOINT: + case Type::HORIZONTAL: + case Type::VERTICAL: + case Type::ANGLE: + case Type::PARALLEL: + case Type::PERPENDICULAR: + case Type::WHERE_DRAGGED: + case Type::COMMENT: + return true; + + case Type::PT_PLANE_DISTANCE: + case Type::PT_FACE_DISTANCE: + case Type::PROJ_PT_DISTANCE: + case Type::PT_IN_PLANE: + case Type::PT_ON_FACE: + case Type::EQUAL_LINE_ARC_LEN: + case Type::DIAMETER: + case Type::PT_ON_CIRCLE: + case Type::SAME_ORIENTATION: + case Type::CUBIC_LINE_TANGENT: + case Type::CURVE_CURVE_TANGENT: + case Type::ARC_LINE_TANGENT: + case Type::EQUAL_RADIUS: + return false; } + ssassert(false, "Impossible"); +} - if(eq == 0) return e0; - if(eq == 1) return e1; - oops(); +ExprVector ConstraintBase::VectorsParallel3d(ExprVector a, ExprVector b, hParam p) { + return a.Minus(b.ScaledBy(Expr::From(p))); } Expr *ConstraintBase::PointLineDistance(hEntity wrkpl, hEntity hpt, hEntity hln) @@ -65,7 +84,7 @@ Expr *ConstraintBase::PointLineDistance(hEntity wrkpl, hEntity hpt, hEntity hln) EntityBase *p = SK.GetEntity(hpt); - if(wrkpl.v == EntityBase::FREE_IN_3D.v) { + if(wrkpl == EntityBase::FREE_IN_3D) { ExprVector ep = p->PointGetExprs(); ExprVector ea = a->PointGetExprs(); @@ -104,9 +123,10 @@ Expr *ConstraintBase::PointPlaneDistance(ExprVector p, hEntity hpl) { Expr *ConstraintBase::Distance(hEntity wrkpl, hEntity hpa, hEntity hpb) { EntityBase *pa = SK.GetEntity(hpa); EntityBase *pb = SK.GetEntity(hpb); - if(!(pa->IsPoint() && pb->IsPoint())) oops(); + ssassert(pa->IsPoint() && pb->IsPoint(), + "Expected two points to measure projected distance between"); - if(wrkpl.v == EntityBase::FREE_IN_3D.v) { + if(wrkpl == EntityBase::FREE_IN_3D) { // This is true distance ExprVector ea, eb, eab; ea = pa->PointGetExprs(); @@ -135,7 +155,7 @@ Expr *ConstraintBase::Distance(hEntity wrkpl, hEntity hpa, hEntity hpb) { Expr *ConstraintBase::DirectionCosine(hEntity wrkpl, ExprVector ae, ExprVector be) { - if(wrkpl.v == EntityBase::FREE_IN_3D.v) { + if(wrkpl == EntityBase::FREE_IN_3D) { Expr *mags = (ae.Magnitude())->Times(be.Magnitude()); return (ae.Dot(be))->Div(mags); } else { @@ -165,34 +185,44 @@ ExprVector ConstraintBase::PointInThreeSpace(hEntity workplane, return (ub.ScaledBy(u)).Plus(vb.ScaledBy(v)).Plus(ob); } -void ConstraintBase::ModifyToSatisfy(void) { - if(type == ANGLE) { +void ConstraintBase::ModifyToSatisfy() { + if(type == Type::ANGLE) { Vector a = SK.GetEntity(entityA)->VectorGetNum(); Vector b = SK.GetEntity(entityB)->VectorGetNum(); if(other) a = a.ScaledBy(-1); - if(workplane.v != EntityBase::FREE_IN_3D.v) { + if(workplane != EntityBase::FREE_IN_3D) { a = a.ProjectVectorInto(workplane); b = b.ProjectVectorInto(workplane); } double c = (a.Dot(b))/(a.Magnitude() * b.Magnitude()); valA = acos(c)*180/PI; + } else if(type == Type::PT_ON_LINE) { + EntityBase *eln = SK.GetEntity(entityA); + EntityBase *ea = SK.GetEntity(eln->point[0]); + EntityBase *eb = SK.GetEntity(eln->point[1]); + EntityBase *ep = SK.GetEntity(ptA); + ExprVector exp = ep->PointGetExprsInWorkplane(workplane); + ExprVector exa = ea->PointGetExprsInWorkplane(workplane); + ExprVector exb = eb->PointGetExprsInWorkplane(workplane); + ExprVector exba = exb.Minus(exa); + SK.GetParam(valP)->val = exba.Dot(exp.Minus(exa))->Eval() / exba.Dot(exba)->Eval(); } else { // We'll fix these ones up by looking at their symbolic equation; // that means no extra work. IdList l = {}; // Generate the equations even if this is a reference dimension - GenerateReal(&l); - if(l.n != 1) oops(); + GenerateEquations(&l, /*forReference=*/true); + ssassert(l.n == 1, "Expected constraint to generate a single equation"); // These equations are written in the form f(...) - d = 0, where // d is the value of the valA. - valA += (l.elem[0].e)->Eval(); + valA += (l[0].e)->Eval(); l.Clear(); } } -void ConstraintBase::AddEq(IdList *l, Expr *expr, int index) +void ConstraintBase::AddEq(IdList *l, Expr *expr, int index) const { Equation eq; eq.e = expr; @@ -200,20 +230,47 @@ void ConstraintBase::AddEq(IdList *l, Expr *expr, int index) l->Add(&eq); } -void ConstraintBase::Generate(IdList *l) { - if(!reference) { - GenerateReal(l); +void ConstraintBase::AddEq(IdList *l, const ExprVector &v, + int baseIndex) const { + AddEq(l, v.x, baseIndex); + AddEq(l, v.y, baseIndex + 1); + if(workplane == EntityBase::FREE_IN_3D) { + AddEq(l, v.z, baseIndex + 2); } } -void ConstraintBase::GenerateReal(IdList *l) { - Expr *exA = Expr::From(valA); +void ConstraintBase::Generate(IdList *l) { switch(type) { - case PT_PT_DISTANCE: - AddEq(l, Distance(workplane, ptA, ptB)->Minus(exA), 0); + case Type::PARALLEL: + case Type::CUBIC_LINE_TANGENT: + // Add new parameter only when we operate in 3d space + if(workplane != EntityBase::FREE_IN_3D) break; + // fallthrough + case Type::SAME_ORIENTATION: + case Type::PT_ON_LINE: { + Param p = {}; + valP = h.param(0); + p.h = valP; + l->Add(&p); + break; + } + + default: break; + } +} + +void ConstraintBase::GenerateEquations(IdList *l, + bool forReference) const { + if(reference && !forReference) return; + + Expr *exA = Expr::From(valA); + switch(type) { + case Type::PT_PT_DISTANCE: + AddEq(l, Distance(workplane, ptA, ptB)->Minus(exA), 0); + return; - case PROJ_PT_DISTANCE: { + case Type::PROJ_PT_DISTANCE: { ExprVector pA = SK.GetEntity(ptA)->PointGetExprs(), pB = SK.GetEntity(ptB)->PointGetExprs(), dp = pB.Minus(pA); @@ -222,87 +279,87 @@ void ConstraintBase::GenerateReal(IdList *l) { pp = pp.WithMagnitude(Expr::From(1.0)); AddEq(l, (dp.Dot(pp))->Minus(exA), 0); - break; + return; } - case PT_LINE_DISTANCE: + case Type::PT_LINE_DISTANCE: AddEq(l, PointLineDistance(workplane, ptA, entityA)->Minus(exA), 0); - break; + return; - case PT_PLANE_DISTANCE: { + case Type::PT_PLANE_DISTANCE: { ExprVector pt = SK.GetEntity(ptA)->PointGetExprs(); AddEq(l, (PointPlaneDistance(pt, entityA))->Minus(exA), 0); - break; + return; } - case PT_FACE_DISTANCE: { + case Type::PT_FACE_DISTANCE: { ExprVector pt = SK.GetEntity(ptA)->PointGetExprs(); EntityBase *f = SK.GetEntity(entityA); ExprVector p0 = f->FaceGetPointExprs(); ExprVector n = f->FaceGetNormalExprs(); AddEq(l, (pt.Minus(p0)).Dot(n)->Minus(exA), 0); - break; + return; } - case EQUAL_LENGTH_LINES: { + case Type::EQUAL_LENGTH_LINES: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); AddEq(l, Distance(workplane, a->point[0], a->point[1])->Minus( Distance(workplane, b->point[0], b->point[1])), 0); - break; + return; } // These work on distance squared, since the pt-line distances are // signed, and we want the absolute value. - case EQ_LEN_PT_LINE_D: { + case Type::EQ_LEN_PT_LINE_D: { EntityBase *forLen = SK.GetEntity(entityA); Expr *d1 = Distance(workplane, forLen->point[0], forLen->point[1]); Expr *d2 = PointLineDistance(workplane, ptA, entityB); AddEq(l, (d1->Square())->Minus(d2->Square()), 0); - break; + return; } - case EQ_PT_LN_DISTANCES: { + case Type::EQ_PT_LN_DISTANCES: { Expr *d1 = PointLineDistance(workplane, ptA, entityA); Expr *d2 = PointLineDistance(workplane, ptB, entityB); AddEq(l, (d1->Square())->Minus(d2->Square()), 0); - break; + return; } - case LENGTH_RATIO: { + case Type::LENGTH_RATIO: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); Expr *la = Distance(workplane, a->point[0], a->point[1]); Expr *lb = Distance(workplane, b->point[0], b->point[1]); AddEq(l, (la->Div(lb))->Minus(exA), 0); - break; + return; } - case LENGTH_DIFFERENCE: { + case Type::LENGTH_DIFFERENCE: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); Expr *la = Distance(workplane, a->point[0], a->point[1]); Expr *lb = Distance(workplane, b->point[0], b->point[1]); AddEq(l, (la->Minus(lb))->Minus(exA), 0); - break; + return; } - case DIAMETER: { + case Type::DIAMETER: { EntityBase *circle = SK.GetEntity(entityA); Expr *r = circle->CircleGetRadiusExpr(); AddEq(l, (r->Times(Expr::From(2)))->Minus(exA), 0); - break; + return; } - case EQUAL_RADIUS: { + case Type::EQUAL_RADIUS: { EntityBase *c1 = SK.GetEntity(entityA); EntityBase *c2 = SK.GetEntity(entityB); AddEq(l, (c1->CircleGetRadiusExpr())->Minus( c2->CircleGetRadiusExpr()), 0); - break; + return; } - case EQUAL_LINE_ARC_LEN: { + case Type::EQUAL_LINE_ARC_LEN: { EntityBase *line = SK.GetEntity(entityA), *arc = SK.GetEntity(entityB); @@ -342,13 +399,13 @@ void ConstraintBase::GenerateReal(IdList *l) { // And write the equation; r*theta = L AddEq(l, (r->Times(theta))->Minus(ll), 0); - break; + return; } - case POINTS_COINCIDENT: { + case Type::POINTS_COINCIDENT: { EntityBase *a = SK.GetEntity(ptA); EntityBase *b = SK.GetEntity(ptB); - if(workplane.v == EntityBase::FREE_IN_3D.v) { + if(workplane == EntityBase::FREE_IN_3D) { ExprVector pa = a->PointGetExprs(); ExprVector pb = b->PointGetExprs(); AddEq(l, pa.x->Minus(pb.x), 0); @@ -362,58 +419,43 @@ void ConstraintBase::GenerateReal(IdList *l) { AddEq(l, au->Minus(bu), 0); AddEq(l, av->Minus(bv), 1); } - break; + return; } - case PT_IN_PLANE: + case Type::PT_IN_PLANE: // This one works the same, whether projected or not. AddEq(l, PointPlaneDistance( SK.GetEntity(ptA)->PointGetExprs(), entityA), 0); - break; + return; - case PT_ON_FACE: { + case Type::PT_ON_FACE: { // a plane, n dot (p - p0) = 0 ExprVector p = SK.GetEntity(ptA)->PointGetExprs(); EntityBase *f = SK.GetEntity(entityA); ExprVector p0 = f->FaceGetPointExprs(); ExprVector n = f->FaceGetNormalExprs(); AddEq(l, (p.Minus(p0)).Dot(n), 0); - break; + return; } - case PT_ON_LINE: - if(workplane.v == EntityBase::FREE_IN_3D.v) { - EntityBase *ln = SK.GetEntity(entityA); - EntityBase *a = SK.GetEntity(ln->point[0]); - EntityBase *b = SK.GetEntity(ln->point[1]); - EntityBase *p = SK.GetEntity(ptA); - - ExprVector ep = p->PointGetExprs(); - ExprVector ea = a->PointGetExprs(); - ExprVector eb = b->PointGetExprs(); - ExprVector eab = ea.Minus(eb); - - // Construct a vector from the point to either endpoint of - // the line segment, and choose the longer of these. - ExprVector eap = ea.Minus(ep); - ExprVector ebp = eb.Minus(ep); - ExprVector elp = - (ebp.Magnitude()->Eval() > eap.Magnitude()->Eval()) ? - ebp : eap; - - if(p->group.v == group.v) { - AddEq(l, VectorsParallel(0, eab, elp), 0); - AddEq(l, VectorsParallel(1, eab, elp), 1); - } else { - AddEq(l, VectorsParallel(0, elp, eab), 0); - AddEq(l, VectorsParallel(1, elp, eab), 1); - } - } else { - AddEq(l, PointLineDistance(workplane, ptA, entityA), 0); - } - break; + case Type::PT_ON_LINE: { + EntityBase *ln = SK.GetEntity(entityA); + EntityBase *a = SK.GetEntity(ln->point[0]); + EntityBase *b = SK.GetEntity(ln->point[1]); + EntityBase *p = SK.GetEntity(ptA); + + ExprVector ep = p->PointGetExprsInWorkplane(workplane); + ExprVector ea = a->PointGetExprsInWorkplane(workplane); + ExprVector eb = b->PointGetExprsInWorkplane(workplane); + + ExprVector ptOnLine = ea.Plus(eb.Minus(ea).ScaledBy(Expr::From(valP))); + ExprVector eq = ptOnLine.Minus(ep); - case PT_ON_CIRCLE: { + AddEq(l, eq); + return; + } + + case Type::PT_ON_CIRCLE: { // This actually constrains the point to lie on the cylinder. EntityBase *circle = SK.GetEntity(entityA); ExprVector center = SK.GetEntity(circle->point[0])->PointGetExprs(); @@ -427,13 +469,12 @@ void ConstraintBase::GenerateReal(IdList *l) { Expr *r = circle->CircleGetRadiusExpr(); - AddEq(l, - ((du->Square())->Plus(dv->Square()))->Minus(r->Square()), 0); - break; + AddEq(l, du->Square()->Plus(dv->Square())->Sqrt()->Minus(r), 0); + return; } - case AT_MIDPOINT: - if(workplane.v == EntityBase::FREE_IN_3D.v) { + case Type::AT_MIDPOINT: + if(workplane == EntityBase::FREE_IN_3D) { EntityBase *ln = SK.GetEntity(entityA); ExprVector a = SK.GetEntity(ln->point[0])->PointGetExprs(); ExprVector b = SK.GetEntity(ln->point[1])->PointGetExprs(); @@ -469,10 +510,10 @@ void ConstraintBase::GenerateReal(IdList *l) { AddEq(l, PointPlaneDistance(m, entityB), 0); } } - break; + return; - case SYMMETRIC: - if(workplane.v == EntityBase::FREE_IN_3D.v) { + case Type::SYMMETRIC: + if(workplane == EntityBase::FREE_IN_3D) { EntityBase *plane = SK.GetEntity(entityA); EntityBase *ea = SK.GetEntity(ptA); EntityBase *eb = SK.GetEntity(ptB); @@ -520,10 +561,13 @@ void ConstraintBase::GenerateReal(IdList *l) { plane->WorkplaneGetPlaneExprs(&n, &d); AddEq(l, (n.Cross(u.Cross(v))).Dot(pa.Minus(pb)), 1); } - break; + return; + + case Type::SYMMETRIC_HORIZ: + case Type::SYMMETRIC_VERT: { + ssassert(workplane != Entity::FREE_IN_3D, + "Unexpected horizontal/vertical symmetric constraint in 3d"); - case SYMMETRIC_HORIZ: - case SYMMETRIC_VERT: { EntityBase *a = SK.GetEntity(ptA); EntityBase *b = SK.GetEntity(ptB); @@ -531,17 +575,17 @@ void ConstraintBase::GenerateReal(IdList *l) { a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); - if(type == SYMMETRIC_HORIZ) { + if(type == Type::SYMMETRIC_HORIZ) { AddEq(l, av->Minus(bv), 0); AddEq(l, au->Plus(bu), 1); } else { AddEq(l, au->Minus(bu), 0); AddEq(l, av->Plus(bv), 1); } - break; + return; } - case SYMMETRIC_LINE: { + case Type::SYMMETRIC_LINE: { EntityBase *pa = SK.GetEntity(ptA); EntityBase *pb = SK.GetEntity(ptB); @@ -571,11 +615,14 @@ void ConstraintBase::GenerateReal(IdList *l) { (dlu->Times(lav->Minus(pbv)))); AddEq(l, dista->Plus(distb), 1); - break; + return; } - case HORIZONTAL: - case VERTICAL: { + case Type::HORIZONTAL: + case Type::VERTICAL: { + ssassert(workplane != Entity::FREE_IN_3D, + "Unexpected horizontal/vertical constraint in 3d"); + hEntity ha, hb; if(entityA.v) { EntityBase *e = SK.GetEntity(entityA); @@ -592,16 +639,13 @@ void ConstraintBase::GenerateReal(IdList *l) { a->PointGetExprsInWorkplane(workplane, &au, &av); b->PointGetExprsInWorkplane(workplane, &bu, &bv); - AddEq(l, (type == HORIZONTAL) ? av->Minus(bv) : au->Minus(bu), 0); - break; + AddEq(l, (type == Type::HORIZONTAL) ? av->Minus(bv) : au->Minus(bu), 0); + return; } - case SAME_ORIENTATION: { + case Type::SAME_ORIENTATION: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); - if(b->group.v != group.v) { - swap(a, b); - } ExprVector au = a->NormalExprsU(), an = a->NormalExprsN(); @@ -609,22 +653,24 @@ void ConstraintBase::GenerateReal(IdList *l) { bv = b->NormalExprsV(), bn = b->NormalExprsN(); - AddEq(l, VectorsParallel(0, an, bn), 0); - AddEq(l, VectorsParallel(1, an, bn), 1); + ExprVector eq = VectorsParallel3d(an, bn, valP); + AddEq(l, eq.x, 0); + AddEq(l, eq.y, 1); + AddEq(l, eq.z, 2); Expr *d1 = au.Dot(bv); Expr *d2 = au.Dot(bu); // Allow either orientation for the coordinate system, depending // on how it was drawn. if(fabs(d1->Eval()) < fabs(d2->Eval())) { - AddEq(l, d1, 2); + AddEq(l, d1, 3); } else { - AddEq(l, d2, 2); + AddEq(l, d2, 3); } - break; + return; } - case PERPENDICULAR: - case ANGLE: { + case Type::PERPENDICULAR: + case Type::ANGLE: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); ExprVector ae = a->VectorGetExprs(); @@ -632,7 +678,7 @@ void ConstraintBase::GenerateReal(IdList *l) { if(other) ae = ae.ScaledBy(Expr::From(-1)); Expr *c = DirectionCosine(workplane, ae, be); - if(type == ANGLE) { + if(type == Type::ANGLE) { // The direction cosine is equal to the cosine of the // specified angle Expr *rads = exA->Times(Expr::From(PI/180)), @@ -649,10 +695,10 @@ void ConstraintBase::GenerateReal(IdList *l) { // is equal to zero, perpendicular. AddEq(l, c, 0); } - break; + return; } - case EQUAL_ANGLE: { + case Type::EQUAL_ANGLE: { EntityBase *a = SK.GetEntity(entityA); EntityBase *b = SK.GetEntity(entityB); EntityBase *c = SK.GetEntity(entityC); @@ -668,10 +714,10 @@ void ConstraintBase::GenerateReal(IdList *l) { Expr *ccd = DirectionCosine(workplane, ce, de); AddEq(l, cab->Minus(ccd), 0); - break; + return; } - case ARC_LINE_TANGENT: { + case Type::ARC_LINE_TANGENT: { EntityBase *arc = SK.GetEntity(entityA); EntityBase *line = SK.GetEntity(entityB); @@ -683,10 +729,10 @@ void ConstraintBase::GenerateReal(IdList *l) { // The line is perpendicular to the radius AddEq(l, ld.Dot(ac.Minus(ap)), 0); - break; + return; } - case CUBIC_LINE_TANGENT: { + case Type::CUBIC_LINE_TANGENT: { EntityBase *cubic = SK.GetEntity(entityA); EntityBase *line = SK.GetEntity(entityB); @@ -699,18 +745,18 @@ void ConstraintBase::GenerateReal(IdList *l) { ExprVector b = line->VectorGetExprs(); - if(workplane.v == EntityBase::FREE_IN_3D.v) { - AddEq(l, VectorsParallel(0, a, b), 0); - AddEq(l, VectorsParallel(1, a, b), 1); + if(workplane == EntityBase::FREE_IN_3D) { + ExprVector eq = VectorsParallel3d(a, b, valP); + AddEq(l, eq); } else { EntityBase *w = SK.GetEntity(workplane); ExprVector wn = w->Normal()->NormalExprsN(); AddEq(l, (a.Cross(b)).Dot(wn), 0); } - break; + return; } - case CURVE_CURVE_TANGENT: { + case Type::CURVE_CURVE_TANGENT: { bool parallel = true; int i; ExprVector dir[2]; @@ -718,7 +764,7 @@ void ConstraintBase::GenerateReal(IdList *l) { EntityBase *e = SK.GetEntity((i == 0) ? entityA : entityB); bool oth = (i == 0) ? other : other2; - if(e->type == Entity::ARC_OF_CIRCLE) { + if(e->type == Entity::Type::ARC_OF_CIRCLE) { ExprVector center, endpoint; center = SK.GetEntity(e->point[0])->PointGetExprs(); endpoint = @@ -728,14 +774,14 @@ void ConstraintBase::GenerateReal(IdList *l) { // an endpoint; so that's normal to the tangent, not // parallel. parallel = !parallel; - } else if(e->type == Entity::CUBIC) { + } else if(e->type == Entity::Type::CUBIC) { // BRANCH_ALWAYS_TAKEN if(oth) { dir[i] = e->CubicGetFinishTangentExprs(); } else { dir[i] = e->CubicGetStartTangentExprs(); } } else { - oops(); + ssassert(false, "Unexpected entity types for CURVE_CURVE_TANGENT"); } } if(parallel) { @@ -745,31 +791,35 @@ void ConstraintBase::GenerateReal(IdList *l) { } else { AddEq(l, (dir[0]).Dot(dir[1]), 0); } - break; + return; } - case PARALLEL: { + case Type::PARALLEL: { EntityBase *ea = SK.GetEntity(entityA), *eb = SK.GetEntity(entityB); - if(eb->group.v != group.v) { - swap(ea, eb); - } - ExprVector a = ea->VectorGetExprs(); - ExprVector b = eb->VectorGetExprs(); + ExprVector a = ea->VectorGetExprsInWorkplane(workplane); + ExprVector b = eb->VectorGetExprsInWorkplane(workplane); - if(workplane.v == EntityBase::FREE_IN_3D.v) { - AddEq(l, VectorsParallel(0, a, b), 0); - AddEq(l, VectorsParallel(1, a, b), 1); + if(workplane == EntityBase::FREE_IN_3D) { + ExprVector eq = VectorsParallel3d(a, b, valP); + AddEq(l, eq); } else { - EntityBase *w = SK.GetEntity(workplane); - ExprVector wn = w->Normal()->NormalExprsN(); - AddEq(l, (a.Cross(b)).Dot(wn), 0); + // We use expressions written in workplane csys, so we can assume the workplane + // normal is (0, 0, 1). We can write the equation as: + // Expr *eq = a.Cross(b).Dot(ExprVector::From(0.0, 0.0, 1.0)); + // but this will just result in elimination of x and y terms after dot product. + // We can only use the z expression: + // Expr *eq = a.Cross(b).z; + // but it's more efficient to write it in the terms of pseudo-scalar product: + Expr *eq = (a.x->Times(b.y))->Minus(a.y->Times(b.x)); + AddEq(l, eq, 0); } - break; + + return; } - case WHERE_DRAGGED: { + case Type::WHERE_DRAGGED: { EntityBase *ep = SK.GetEntity(ptA); - if(workplane.v == EntityBase::FREE_IN_3D.v) { + if(workplane == EntityBase::FREE_IN_3D) { ExprVector ev = ep->PointGetExprs(); Vector v = ep->PointGetNum(); @@ -782,13 +832,12 @@ void ConstraintBase::GenerateReal(IdList *l) { AddEq(l, u->Minus(Expr::From(u->Eval())), 0); AddEq(l, v->Minus(Expr::From(v->Eval())), 1); } - break; + return; } - case COMMENT: - break; - - default: oops(); + case Type::COMMENT: + return; } + ssassert(false, "Unexpected constraint ID"); } diff --git a/src/describescreen.cpp b/src/describescreen.cpp index b91993f..17f7361 100644 --- a/src/describescreen.cpp +++ b/src/describescreen.cpp @@ -7,7 +7,7 @@ #include "solvespace.h" void TextWindow::ScreenUnselectAll(int link, uint32_t v) { - GraphicsWindow::MenuEdit(GraphicsWindow::MNU_UNSELECT_ALL); + GraphicsWindow::MenuEdit(Command::UNSELECT_ALL); } void TextWindow::ScreenEditTtfText(int link, uint32_t v) { @@ -15,29 +15,38 @@ void TextWindow::ScreenEditTtfText(int link, uint32_t v) { Request *r = SK.GetRequest(hr); SS.TW.ShowEditControl(10, r->str); - SS.TW.edit.meaning = EDIT_TTF_TEXT; + SS.TW.edit.meaning = Edit::TTF_TEXT; SS.TW.edit.request = hr; } -#define gs (SS.GW.gs) void TextWindow::ScreenSetTtfFont(int link, uint32_t v) { int i = (int)v; if(i < 0) return; if(i >= SS.fonts.l.n) return; SS.GW.GroupSelection(); + auto const &gs = SS.GW.gs; if(gs.entities != 1 || gs.n != 1) return; Entity *e = SK.entity.FindByIdNoOops(gs.entity[0]); - if(!e || e->type != Entity::TTF_TEXT || !e->h.isFromRequest()) return; + if(!e || e->type != Entity::Type::TTF_TEXT || !e->h.isFromRequest()) return; Request *r = SK.request.FindByIdNoOops(e->h.request()); if(!r) return; SS.UndoRemember(); - r->font = SS.fonts.l.elem[i].FontFileBaseName(); + r->font = SS.fonts.l[i].FontFileBaseName(); SS.MarkGroupDirty(r->group); - SS.ScheduleGenerateAll(); + SS.ScheduleShowTW(); +} + +void TextWindow::ScreenConstraintToggleReference(int link, uint32_t v) { + hConstraint hc = { v }; + Constraint *c = SK.GetConstraint(hc); + + SS.UndoRemember(); + c->reference = !c->reference; + SS.ScheduleShowTW(); } @@ -51,14 +60,13 @@ void TextWindow::ScreenConstraintShowAsRadius(int link, uint32_t v) { SS.ScheduleShowTW(); } -void TextWindow::DescribeSelection(void) { - Entity *e; - Vector p; - int i; +void TextWindow::DescribeSelection() { Printf(false, ""); + auto const &gs = SS.GW.gs; if(gs.n == 1 && (gs.points == 1 || gs.entities == 1)) { - e = SK.GetEntity(gs.points == 1 ? gs.point[0] : gs.entity[0]); + Entity *e = SK.GetEntity(gs.points == 1 ? gs.point[0] : gs.entity[0]); + Vector p; #define COSTR(p) \ SS.MmToString((p).x).c_str(), \ @@ -67,21 +75,21 @@ void TextWindow::DescribeSelection(void) { #define PT_AS_STR "(%Fi%s%E, %Fi%s%E, %Fi%s%E)" #define PT_AS_NUM "(%Fi%3%E, %Fi%3%E, %Fi%3%E)" switch(e->type) { - case Entity::POINT_IN_3D: - case Entity::POINT_IN_2D: - case Entity::POINT_N_TRANS: - case Entity::POINT_N_ROT_TRANS: - case Entity::POINT_N_COPY: - case Entity::POINT_N_ROT_AA: + case Entity::Type::POINT_IN_3D: + case Entity::Type::POINT_IN_2D: + case Entity::Type::POINT_N_TRANS: + case Entity::Type::POINT_N_ROT_TRANS: + case Entity::Type::POINT_N_COPY: + case Entity::Type::POINT_N_ROT_AA: p = e->PointGetNum(); Printf(false, "%FtPOINT%E at " PT_AS_STR, COSTR(p)); break; - case Entity::NORMAL_IN_3D: - case Entity::NORMAL_IN_2D: - case Entity::NORMAL_N_COPY: - case Entity::NORMAL_N_ROT: - case Entity::NORMAL_N_ROT_AA: { + case Entity::Type::NORMAL_IN_3D: + case Entity::Type::NORMAL_IN_2D: + case Entity::Type::NORMAL_N_COPY: + case Entity::Type::NORMAL_N_ROT: + case Entity::Type::NORMAL_N_ROT_AA: { Quaternion q = e->NormalGetNum(); p = q.RotationN(); Printf(false, "%FtNORMAL / COORDINATE SYSTEM%E"); @@ -92,7 +100,7 @@ void TextWindow::DescribeSelection(void) { Printf(false, " v = " PT_AS_NUM, CO(p)); break; } - case Entity::WORKPLANE: { + case Entity::Type::WORKPLANE: { p = SK.GetEntity(e->point[0])->PointGetNum(); Printf(false, "%FtWORKPLANE%E"); Printf(true, " origin = " PT_AS_STR, COSTR(p)); @@ -101,7 +109,7 @@ void TextWindow::DescribeSelection(void) { Printf(true, " normal = " PT_AS_NUM, CO(p)); break; } - case Entity::LINE_SEGMENT: { + case Entity::Type::LINE_SEGMENT: { Vector p0 = SK.GetEntity(e->point[0])->PointGetNum(); p = p0; Printf(false, "%FtLINE SEGMENT%E"); @@ -113,10 +121,10 @@ void TextWindow::DescribeSelection(void) { SS.MmToString((p1.Minus(p0).Magnitude())).c_str()); break; } - case Entity::CUBIC_PERIODIC: - case Entity::CUBIC: + case Entity::Type::CUBIC_PERIODIC: + case Entity::Type::CUBIC: int pts; - if(e->type == Entity::CUBIC_PERIODIC) { + if(e->type == Entity::Type::CUBIC_PERIODIC) { Printf(false, "%FtPERIODIC C2 CUBIC SPLINE%E"); pts = (3 + e->extraPoints); } else if(e->extraPoints > 0) { @@ -126,13 +134,13 @@ void TextWindow::DescribeSelection(void) { Printf(false, "%FtCUBIC BEZIER CURVE%E"); pts = 4; } - for(i = 0; i < pts; i++) { + for(int i = 0; i < pts; i++) { p = SK.GetEntity(e->point[i])->PointGetNum(); Printf((i==0), " p%d = " PT_AS_STR, i, COSTR(p)); } break; - case Entity::ARC_OF_CIRCLE: { + case Entity::Type::ARC_OF_CIRCLE: { Printf(false, "%FtARC OF A CIRCLE%E"); p = SK.GetEntity(e->point[0])->PointGetNum(); Printf(true, " center = " PT_AS_STR, COSTR(p)); @@ -148,7 +156,7 @@ void TextWindow::DescribeSelection(void) { Printf(false, " arc len = %Fi%s", SS.MmToString(dtheta*r).c_str()); break; } - case Entity::CIRCLE: { + case Entity::Type::CIRCLE: { Printf(false, "%FtCIRCLE%E"); p = SK.GetEntity(e->point[0])->PointGetNum(); Printf(true, " center = " PT_AS_STR, COSTR(p)); @@ -157,11 +165,11 @@ void TextWindow::DescribeSelection(void) { Printf(false, " radius = %Fi%s", SS.MmToString(r).c_str()); break; } - case Entity::FACE_NORMAL_PT: - case Entity::FACE_XPROD: - case Entity::FACE_N_ROT_TRANS: - case Entity::FACE_N_ROT_AA: - case Entity::FACE_N_TRANS: + case Entity::Type::FACE_NORMAL_PT: + case Entity::Type::FACE_XPROD: + case Entity::Type::FACE_N_ROT_TRANS: + case Entity::Type::FACE_N_ROT_AA: + case Entity::Type::FACE_N_TRANS: Printf(false, "%FtPLANE FACE%E"); p = e->FaceGetNormalNum(); Printf(true, " normal = " PT_AS_NUM, CO(p)); @@ -169,7 +177,7 @@ void TextWindow::DescribeSelection(void) { Printf(false, " thru = " PT_AS_STR, COSTR(p)); break; - case Entity::TTF_TEXT: { + case Entity::Type::TTF_TEXT: { Printf(false, "%FtTRUETYPE FONT TEXT%E"); Printf(true, " font = '%Fi%s%E'", e->font.c_str()); if(e->h.isFromRequest()) { @@ -177,9 +185,9 @@ void TextWindow::DescribeSelection(void) { e->str.c_str(), &ScreenEditTtfText, e->h.request().v); Printf(true, " select new font"); SS.fonts.LoadAll(); - int i; - for(i = 0; i < SS.fonts.l.n; i++) { - TtfFont *tf = &(SS.fonts.l.elem[i]); + // Not using range-for here because we use i inside the output. + for(int i = 0; i < SS.fonts.l.n; i++) { + TtfFont *tf = &(SS.fonts.l[i]); if(e->font == tf->FontFileBaseName()) { Printf(false, "%Bp %s", (i & 1) ? 'd' : 'a', @@ -197,30 +205,112 @@ void TextWindow::DescribeSelection(void) { } break; } + case Entity::Type::IMAGE: { + Printf(false, "%FtIMAGE%E"); + Platform::Path relativePath = e->file.RelativeTo(SS.saveFile.Parent()); + if(relativePath.IsEmpty()) { + Printf(true, " file = '%Fi%s%E'", e->file.raw.c_str()); + } else { + Printf(true, " file = '%Fi%s%E'", relativePath.raw.c_str()); + } + break; + } default: Printf(true, "%Ft?? ENTITY%E"); break; } - Group *g = SK.GetGroup(e->group); Printf(false, ""); - Printf(false, "%FtIN GROUP%E %s", g->DescriptionString().c_str()); - if(e->workplane.v == Entity::FREE_IN_3D.v) { + if(e->h.isFromRequest()) { + Request *r = SK.GetRequest(e->h.request()); + if(e->h == r->h.entity(0)) { + Printf(false, "%FtFROM REQUEST%E %s", + r->DescriptionString().c_str()); + } else { + Printf(false, "%FtFROM REQUEST%E %Fl%Ll%D%f%h%s%E", + r->h.v, (&TextWindow::ScreenSelectRequest), &(TextWindow::ScreenHoverRequest), + r->DescriptionString().c_str()); + } + } + Group *g = SK.GetGroup(e->group); + Printf(false, "%FtIN GROUP%E %Fl%Ll%D%f%s%E", + g->h.v, (&TextWindow::ScreenSelectGroup), + g->DescriptionString().c_str()); + if(e->workplane == Entity::FREE_IN_3D) { Printf(false, "%FtNOT LOCKED IN WORKPLANE%E"); } else { Entity *w = SK.GetEntity(e->workplane); - Printf(false, "%FtIN WORKPLANE%E %s", w->DescriptionString().c_str()); + if(w->h.isFromRequest()) { + Printf(false, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E", + w->h.request().v, + (&TextWindow::ScreenSelectRequest), &(TextWindow::ScreenHoverRequest), + w->DescriptionString().c_str()); + } else { + Printf(false, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E", + w->h.group().v, + (&TextWindow::ScreenSelectGroup), (&TextWindow::ScreenHoverGroupWorkplane), + w->DescriptionString().c_str()); + } } - if(e->style.v) { - Style *s = Style::Get(e->style); - Printf(false, "%FtIN STYLE%E %s", s->DescriptionString().c_str()); - } else { - Printf(false, "%FtIN STYLE%E none"); + if(e->IsStylable()) { + if(e->style.v) { + Style *s = Style::Get(e->style); + Printf(false, "%FtIN STYLE%E %Fl%Ll%D%f%s%E", + s->h.v, (&TextWindow::ScreenShowStyleInfo), + s->DescriptionString().c_str()); + } else { + Printf(false, "%FtIN STYLE%E none"); + } } if(e->construction) { Printf(false, "%FtCONSTRUCTION"); } + + std::vector lhc = {}; + auto FindConstraints = [&](hEntity he) { + for(const Constraint &c : SK.constraint) { + if(!(c.ptA == he || c.ptB == he || + c.entityA == he || c.entityB == he || c.entityC == he || c.entityD == he)) + continue; + lhc.push_back(c.h); + } + }; + FindConstraints(e->h); + if(!e->IsPoint()) { + for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) { + if(e->point[i].v == 0) break; + FindConstraints(e->point[i]); + } + } + + std::sort(lhc.begin(), lhc.end()); + lhc.erase(std::unique(lhc.begin(), lhc.end()), lhc.end()); + + auto ListConstraints = [&](bool reference) { + bool first = true; + int a = 0; + for(hConstraint hc : lhc) { + Constraint *c = SK.GetConstraint(hc); + if(c->reference != reference) continue; + if(first) { + first = false; + if(reference) { + Printf(true, "%FtMEASURED BY:%E"); + } else { + Printf(true, "%FtCONSTRAINED BY:%E"); + } + } + Printf(false, "%Bp %Fl%Ll%D%f%h%s%E", + (a & 1) ? 'd' : 'a', + c->h.v, (&TextWindow::ScreenSelectConstraint), + (&TextWindow::ScreenHoverConstraint), + c->DescriptionString().c_str()); + a++; + } + }; + ListConstraints(/*reference=*/false); + ListConstraints(/*reference=*/true); } else if(gs.n == 2 && gs.points == 2) { Printf(false, "%FtTWO POINTS"); Vector p0 = SK.GetEntity(gs.point[0])->PointGetNum(); @@ -231,11 +321,11 @@ void TextWindow::DescribeSelection(void) { Printf(true, " d = %Fi%s", SS.MmToString(d).c_str()); } else if(gs.n == 2 && gs.points == 1 && gs.circlesOrArcs == 1) { Entity *ec = SK.GetEntity(gs.entity[0]); - if(ec->type == Entity::CIRCLE) { + if(ec->type == Entity::Type::CIRCLE) { Printf(false, "%FtPOINT AND A CIRCLE"); - } else if(ec->type == Entity::ARC_OF_CIRCLE) { + } else if(ec->type == Entity::Type::ARC_OF_CIRCLE) { Printf(false, "%FtPOINT AND AN ARC"); - } else oops(); + } else ssassert(false, "Unexpected entity type"); Vector p = SK.GetEntity(gs.point[0])->PointGetNum(); Printf(true, " pt at " PT_AS_STR, COSTR(p)); Vector c = SK.GetEntity(ec->point[0])->PointGetNum(); @@ -273,10 +363,19 @@ void TextWindow::DescribeSelection(void) { Printf(false, "%FtLINE SEGMENT AND POINT%E"); Printf(true, " ln thru " PT_AS_STR, COSTR(lp0)); Printf(false, " " PT_AS_STR, COSTR(lp1)); - Vector pp = SK.GetEntity(gs.point[0])->PointGetNum(); + Entity *p = SK.GetEntity(gs.point[0]); + Vector pp = p->PointGetNum(); Printf(true, " point " PT_AS_STR, COSTR(pp)); Printf(true, " pt-ln distance = %Fi%s%E", SS.MmToString(pp.DistanceToLine(lp0, lp1.Minus(lp0))).c_str()); + hEntity wrkpl = SS.GW.ActiveWorkplane(); + if(wrkpl != Entity::FREE_IN_3D && !(p->workplane == wrkpl && ln->workplane == wrkpl)) { + Vector ppw = pp.ProjectInto(wrkpl); + Vector lp0w = lp0.ProjectInto(wrkpl); + Vector lp1w = lp1.ProjectInto(wrkpl); + Printf(false, " or distance = %Fi%s%E (in workplane)", + SS.MmToString(ppw.DistanceToLine(lp0w, lp1w.Minus(lp0w))).c_str()); + } } else if(gs.n == 2 && gs.vectors == 2) { Printf(false, "%FtTWO VECTORS"); @@ -320,23 +419,84 @@ void TextWindow::DescribeSelection(void) { Printf(false, "%FtSELECTED:%E comment text"); } else if(gs.n == 0 && gs.constraints == 1) { Constraint *c = SK.GetConstraint(gs.constraint[0]); + const std::string &desc = c->DescriptionString().c_str(); + + if(c->type == Constraint::Type::COMMENT) { + Printf(false, "%FtCOMMENT%E %s", desc.c_str()); + } else if(c->HasLabel()) { + if(c->reference) { + Printf(false, "%FtREFERENCE%E %s", desc.c_str()); + } else { + Printf(false, "%FtDIMENSION%E %s", desc.c_str()); + } + Printf(true, " %Fd%f%D%Ll%s reference", + &ScreenConstraintToggleReference, gs.constraint[0].v, + c->reference ? CHECK_TRUE : CHECK_FALSE); + if(c->type == Constraint::Type::DIAMETER) { + Printf(false, " %Fd%f%D%Ll%s use radius", + &ScreenConstraintShowAsRadius, gs.constraint[0].v, + c->other ? CHECK_TRUE : CHECK_FALSE); + } + } else { + Printf(false, "%FtCONSTRAINT%E %s", desc.c_str()); + } + + if(c->IsProjectible()) { + if(c->workplane == Entity::FREE_IN_3D) { + Printf(true, "%FtNOT PROJECTED TO WORKPLANE%E"); + } else { + Entity *w = SK.GetEntity(c->workplane); + if(w->h.isFromRequest()) { + Printf(true, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E", + w->h.request().v, + (&TextWindow::ScreenSelectRequest), &(TextWindow::ScreenHoverRequest), + w->DescriptionString().c_str()); + } else { + Printf(true, "%FtIN WORKPLANE%E %Fl%Ll%D%f%h%s%E", + w->h.group().v, + (&TextWindow::ScreenSelectGroup), (&TextWindow::ScreenHoverGroupWorkplane), + w->DescriptionString().c_str()); + } + } + } - if(c->type == Constraint::DIAMETER) { - Printf(false, "%FtDIAMETER CONSTRAINT"); + std::vector lhe = {}; + lhe.push_back(c->ptA); + lhe.push_back(c->ptB); + lhe.push_back(c->entityA); + lhe.push_back(c->entityB); + lhe.push_back(c->entityC); + lhe.push_back(c->entityD); + + auto it = std::remove_if(lhe.begin(), lhe.end(), [](hEntity he) { + return he == Entity::NO_ENTITY || !he.isFromRequest(); + }); + lhe.erase(it, lhe.end()); + + if(!lhe.empty()) { + if(c->reference) { + Printf(true, "%FtMEASURES:%E"); + } else { + Printf(true, "%FtCONSTRAINS:%E"); + } - Printf(true, " %Fd%f%D%Ll%s show as radius", - &ScreenConstraintShowAsRadius, gs.constraint[0].v, - c->other ? CHECK_TRUE : CHECK_FALSE); - } else { - Printf(false, "%FtSELECTED:%E %s", - c->DescriptionString().c_str()); + int a = 0; + for(hEntity he : lhe) { + Entity *e = SK.GetEntity(he); + Printf(false, "%Bp %Fl%Ll%D%f%h%s%E", + (a & 1) ? 'd' : 'a', + e->h.v, (&TextWindow::ScreenSelectEntity), + &(TextWindow::ScreenHoverEntity), + e->DescriptionString().c_str()); + a++; + } } } else { int n = SS.GW.selection.n; Printf(false, "%FtSELECTED:%E %d item%s", n, n == 1 ? "" : "s"); } - if(shown.screen == SCREEN_STYLE_INFO && + if(shown.screen == Screen::STYLE_INFO && shown.style.v >= Style::FIRST_CUSTOM && gs.stylables > 0) { // If we are showing a screen for a particular style, then offer the @@ -350,15 +510,15 @@ void TextWindow::DescribeSelection(void) { // If any of the selected entities have an assigned style, then offer // the option to remove that style. bool styleAssigned = false; - for(i = 0; i < gs.entities; i++) { + for(int i = 0; i < gs.entities; i++) { Entity *e = SK.GetEntity(gs.entity[i]); if(e->style.v != 0) { styleAssigned = true; } } - for(i = 0; i < gs.constraints; i++) { + for(int i = 0; i < gs.constraints; i++) { Constraint *c = SK.GetConstraint(gs.constraint[i]); - if(c->type == Constraint::COMMENT && c->disp.style.v != 0) { + if(c->type == Constraint::Type::COMMENT && c->disp.style.v != 0) { styleAssigned = true; } } @@ -371,7 +531,7 @@ void TextWindow::DescribeSelection(void) { Printf(true, "%Fl%f%Ll(unselect all)%E", &TextWindow::ScreenUnselectAll); } -void TextWindow::GoToScreen(int screen) { +void TextWindow::GoToScreen(Screen screen) { shown.screen = screen; } diff --git a/src/draw.cpp b/src/draw.cpp index 64f10ca..0a21c19 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -8,67 +8,82 @@ #include "solvespace.h" bool GraphicsWindow::Selection::Equals(Selection *b) { - if(entity.v != b->entity.v) return false; - if(constraint.v != b->constraint.v) return false; + if(entity != b->entity) return false; + if(constraint != b->constraint) return false; return true; } -bool GraphicsWindow::Selection::IsEmpty(void) { +bool GraphicsWindow::Selection::IsEmpty() { if(entity.v) return false; if(constraint.v) return false; return true; } -bool GraphicsWindow::Selection::HasEndpoints(void) { +bool GraphicsWindow::Selection::HasEndpoints() { if(!entity.v) return false; Entity *e = SK.GetEntity(entity); return e->HasEndpoints(); } -void GraphicsWindow::Selection::Clear(void) { +void GraphicsWindow::Selection::Clear() { entity.v = constraint.v = 0; emphasized = false; } -void GraphicsWindow::Selection::Draw(void) { - Vector refp = Vector::From(0, 0, 0); +void GraphicsWindow::Selection::Draw(bool isHovered, Canvas *canvas) { + const Camera &camera = canvas->GetCamera(); + + std::vector refs; if(entity.v) { Entity *e = SK.GetEntity(entity); - e->Draw(/*drawAsHidden=*/false); - if(emphasized) refp = e->GetReferencePos(); + e->Draw(isHovered ? Entity::DrawAs::HOVERED : + Entity::DrawAs::SELECTED, + canvas); + if(emphasized) { + e->GetReferencePoints(&refs); + } } if(constraint.v) { Constraint *c = SK.GetConstraint(constraint); - c->Draw(); - if(emphasized) refp = c->GetReferencePos(); + c->Draw(isHovered ? Constraint::DrawAs::HOVERED : + Constraint::DrawAs::SELECTED, + canvas); + if(emphasized) { + c->GetReferencePoints(camera, &refs); + } } if(emphasized && (constraint.v || entity.v)) { // We want to emphasize this constraint or entity, by drawing a thick - // line from the top left corner of the screen to the reference point + // line from the top left corner of the screen to the reference point(s) // of that entity or constraint. - double s = 0.501/SS.GW.scale; - Vector topLeft = SS.GW.projRight.ScaledBy(-SS.GW.width*s); - topLeft = topLeft.Plus(SS.GW.projUp.ScaledBy(SS.GW.height*s)); - topLeft = topLeft.Minus(SS.GW.offset); - - ssglLineWidth(40); - RgbaColor rgb = Style::Color(Style::HOVERED); - glColor4d(rgb.redF(), rgb.greenF(), rgb.blueF(), 0.2); - glBegin(GL_LINES); - ssglVertex3v(topLeft); - ssglVertex3v(refp); - glEnd(); - ssglLineWidth(1); + Canvas::Stroke strokeEmphasis = {}; + strokeEmphasis.layer = Canvas::Layer::FRONT; + strokeEmphasis.color = Style::Color(Style::HOVERED).WithAlpha(50); + strokeEmphasis.width = 40; + strokeEmphasis.unit = Canvas::Unit::PX; + Canvas::hStroke hcsEmphasis = canvas->GetStroke(strokeEmphasis); + + Point2d topLeftScreen; + topLeftScreen.x = -(double)camera.width / 2; + topLeftScreen.y = (double)camera.height / 2; + Vector topLeft = camera.UnProjectPoint(topLeftScreen); + + auto it = std::unique(refs.begin(), refs.end(), + [](Vector a, Vector b) { return a.Equals(b); }); + refs.erase(it, refs.end()); + for(Vector p : refs) { + canvas->DrawLine(topLeft, p, hcsEmphasis); + } } } -void GraphicsWindow::ClearSelection(void) { +void GraphicsWindow::ClearSelection() { selection.Clear(); SS.ScheduleShowTW(); - InvalidateGraphics(); + Invalidate(); } -void GraphicsWindow::ClearNonexistentSelectionItems(void) { +void GraphicsWindow::ClearNonexistentSelectionItems() { bool change = false; Selection *s; selection.ClearTags(); @@ -83,7 +98,7 @@ void GraphicsWindow::ClearNonexistentSelectionItems(void) { } } selection.RemoveTagged(); - if(change) InvalidateGraphics(); + if(change) Invalidate(); } //----------------------------------------------------------------------------- @@ -136,7 +151,8 @@ void GraphicsWindow::MakeUnselected(Selection *stog, bool coincidentPointTrick){ Vector ep = e->PointGetNum(); for(s = selection.First(); s; s = selection.NextAfter(s)) { if(!s->entity.v) continue; - if(s->entity.v == stog->entity.v) continue; + if(s->entity == stog->entity) + continue; Entity *se = SK.GetEntity(s->entity); if(!se->IsPoint()) continue; if(ep.Equals(se->PointGetNum())) { @@ -187,56 +203,23 @@ void GraphicsWindow::MakeSelected(Selection *stog) { } //----------------------------------------------------------------------------- -// Select everything that lies within the marquee view-aligned rectangle. For -// points, we test by the point location. For normals, we test by the normal's -// associated point. For anything else, we test by any piecewise linear edge. +// Select everything that lies within the marquee view-aligned rectangle. //----------------------------------------------------------------------------- -void GraphicsWindow::SelectByMarquee(void) { - Point2d begin = ProjectPoint(orig.marqueePoint); - double xmin = min(orig.mouse.x, begin.x), - xmax = max(orig.mouse.x, begin.x), - ymin = min(orig.mouse.y, begin.y), - ymax = max(orig.mouse.y, begin.y); +void GraphicsWindow::SelectByMarquee() { + Point2d marqueePoint = ProjectPoint(orig.marqueePoint); + BBox marqueeBBox = BBox::From(Vector::From(marqueePoint.x, marqueePoint.y, -1), + Vector::From(orig.mouse.x, orig.mouse.y, 1)); Entity *e; for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(e->group.v != SS.GW.activeGroup.v) continue; + if(e->group != SS.GW.activeGroup) continue; if(e->IsFace() || e->IsDistance()) continue; if(!e->IsVisible()) continue; - if(e->IsPoint() || e->IsNormal()) { - Vector p = e->IsPoint() ? e->PointGetNum() : - SK.GetEntity(e->point[0])->PointGetNum(); - Point2d pp = ProjectPoint(p); - if(pp.x >= xmin && pp.x <= xmax && - pp.y >= ymin && pp.y <= ymax) - { - MakeSelected(e->h); - } - } else { - // Use the 3d bounding box test routines, to avoid duplication; - // so let our bounding square become a bounding box that certainly - // includes the z = 0 plane. - Vector ptMin = Vector::From(xmin, ymin, -1), - ptMax = Vector::From(xmax, ymax, 1); - SEdgeList sel = {}; - e->GenerateEdges(&sel, true); - SEdge *se; - for(se = sel.l.First(); se; se = sel.l.NextAfter(se)) { - Point2d ppa = ProjectPoint(se->a), - ppb = ProjectPoint(se->b); - Vector ptA = Vector::From(ppa.x, ppa.y, 0), - ptB = Vector::From(ppb.x, ppb.y, 0); - if(Vector::BoundingBoxIntersectsLine(ptMax, ptMin, - ptA, ptB, true) || - !ptA.OutsideAndNotOn(ptMax, ptMin) || - !ptB.OutsideAndNotOn(ptMax, ptMin)) - { - MakeSelected(e->h); - break; - } - } - sel.Clear(); + bool entityHasBBox; + BBox entityBBox = e->GetOrGenerateScreenBBox(&entityHasBBox); + if(entityHasBBox && entityBBox.Overlaps(marqueeBBox)) { + MakeSelected(e->h); } } } @@ -246,11 +229,11 @@ void GraphicsWindow::SelectByMarquee(void) { // constraints separately, counts of certain types of entities (circles, // lines, etc.), and so on. //----------------------------------------------------------------------------- -void GraphicsWindow::GroupSelection(void) { +void GraphicsWindow::GroupSelection() { gs = {}; int i; for(i = 0; i < selection.n; i++) { - Selection *s = &(selection.elem[i]); + Selection *s = &(selection[i]); if(s->entity.v) { (gs.n)++; @@ -295,17 +278,19 @@ void GraphicsWindow::GroupSelection(void) { // And some aux counts too switch(e->type) { - case Entity::WORKPLANE: (gs.workplanes)++; break; - case Entity::LINE_SEGMENT: (gs.lineSegments)++; break; - case Entity::CUBIC: (gs.cubics)++; break; - case Entity::CUBIC_PERIODIC: (gs.periodicCubics)++; break; + case Entity::Type::WORKPLANE: (gs.workplanes)++; break; + case Entity::Type::LINE_SEGMENT: (gs.lineSegments)++; break; + case Entity::Type::CUBIC: (gs.cubics)++; break; + case Entity::Type::CUBIC_PERIODIC: (gs.periodicCubics)++; break; - case Entity::ARC_OF_CIRCLE: + case Entity::Type::ARC_OF_CIRCLE: (gs.circlesOrArcs)++; (gs.arcs)++; break; - case Entity::CIRCLE: (gs.circlesOrArcs)++; break; + case Entity::Type::CIRCLE: (gs.circlesOrArcs)++; break; + + default: break; } } if(s->constraint.v) { @@ -318,10 +303,105 @@ void GraphicsWindow::GroupSelection(void) { } } +Camera GraphicsWindow::GetCamera() const { + Camera camera = {}; + if(window) { + window->GetContentSize(&camera.width, &camera.height); + camera.pixelRatio = window->GetDevicePixelRatio(); + camera.gridFit = (window->GetDevicePixelRatio() == 1); + } else { // solvespace-cli + camera.width = 297.0; // A4? Whatever... + camera.height = 210.0; + camera.pixelRatio = 1.0; + camera.gridFit = camera.pixelRatio == 1.0; + } + camera.offset = offset; + camera.projUp = projUp; + camera.projRight = projRight; + camera.scale = scale; + camera.tangent = SS.CameraTangent(); + return camera; +} + +Lighting GraphicsWindow::GetLighting() const { + Lighting lighting = {}; + lighting.backgroundColor = SS.backgroundColor; + lighting.ambientIntensity = SS.ambientIntensity; + lighting.lightIntensity[0] = SS.lightIntensity[0]; + lighting.lightIntensity[1] = SS.lightIntensity[1]; + lighting.lightDirection[0] = SS.lightDir[0]; + lighting.lightDirection[1] = SS.lightDir[1]; + return lighting; +} + +GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToSelect() { + Selection sel = {}; + if(hoverList.IsEmpty()) + return sel; + + Group *activeGroup = SK.GetGroup(SS.GW.activeGroup); + int bestOrder = -1; + int bestZIndex = 0; + double bestDepth = VERY_POSITIVE; + + for(const Hover &hov : hoverList) { + hGroup hg = {}; + if(hov.selection.entity.v != 0) { + hg = SK.GetEntity(hov.selection.entity)->group; + } else if(hov.selection.constraint.v != 0) { + hg = SK.GetConstraint(hov.selection.constraint)->group; + } + + Group *g = SK.GetGroup(hg); + if(g->order > activeGroup->order) continue; + if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue; + // we have hov.zIndex is >= best and hov.group is >= best (but not > active group) + if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue; + bestOrder = g->order; + bestZIndex = hov.zIndex; + bestDepth = hov.depth; + sel = hov.selection; + } + return sel; +} + +// This uses the same logic as hovering and static entity selection +// but ignores points known not to be draggable +GraphicsWindow::Selection GraphicsWindow::ChooseFromHoverToDrag() { + Selection sel = {}; + if(hoverList.IsEmpty()) + return sel; + + Group *activeGroup = SK.GetGroup(SS.GW.activeGroup); + int bestOrder = -1; + int bestZIndex = 0; + double bestDepth = VERY_POSITIVE; + + for(const Hover &hov : hoverList) { + hGroup hg = {}; + if(hov.selection.entity.v != 0) { + Entity *e = SK.GetEntity(hov.selection.entity); + if (!e->CanBeDragged()) continue; + hg = e->group; + } else if(hov.selection.constraint.v != 0) { + hg = SK.GetConstraint(hov.selection.constraint)->group; + } + + Group *g = SK.GetGroup(hg); + if(g->order > activeGroup->order) continue; + if(bestOrder != -1 && (bestOrder > g->order || bestZIndex > hov.zIndex)) continue; + // we have hov.zIndex is >= best and hov.group is >= best (but not > active group) + if(hov.depth > bestDepth && bestOrder == g->order && bestZIndex == hov.zIndex) continue; + bestOrder = g->order; + bestZIndex = hov.zIndex; + sel = hov.selection; + } + return sel; +} + void GraphicsWindow::HitTestMakeSelection(Point2d mp) { - int i; - double d, dmin = 1e12; - Selection s = {}; + hoverList = {}; + Selection sel = {}; // Did the view projection change? If so, invalidate bounding boxes. if(!offset.EqualsExactly(cached.offset) || @@ -337,58 +417,82 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) { } } + ObjectPicker canvas = {}; + canvas.camera = GetCamera(); + canvas.selRadius = 10.0; + canvas.point = mp; + canvas.maxZIndex = -1; + // Always do the entities; we might be dragging something that should // be auto-constrained, and we need the hover for that. - for(i = 0; i < SK.entity.n; i++) { - Entity *e = &(SK.entity.elem[i]); + for(Entity &e : SK.entity) { + if(!e.IsVisible()) continue; + + // If faces aren't selectable, image entities aren't either. + if(e.type == Entity::Type::IMAGE && !showFaces) continue; + // Don't hover whatever's being dragged. - if(e->h.request().v == pending.point.request().v) { + if(IsFromPending(e.h.request())) { // The one exception is when we're creating a new cubic; we // want to be able to hover the first point, because that's // how we turn it into a periodic spline. - if(!e->IsPoint()) continue; - if(!e->h.isFromRequest()) continue; - Request *r = SK.GetRequest(e->h.request()); - if(r->type != Request::CUBIC) continue; + if(!e.IsPoint()) continue; + if(!e.h.isFromRequest()) continue; + Request *r = SK.GetRequest(e.h.request()); + if(r->type != Request::Type::CUBIC) continue; if(r->extraPoints < 2) continue; - if(e->h.v != r->h.entity(1).v) continue; + if(e.h.v != r->h.entity(1).v) continue; } - d = e->GetDistance(mp); - if(d < SELECTION_RADIUS && d < dmin) { - s = {}; - s.entity = e->h; - dmin = d; + if(canvas.Pick([&]{ e.Draw(Entity::DrawAs::DEFAULT, &canvas); })) { + Hover hov = {}; + hov.distance = canvas.minDistance; + hov.zIndex = canvas.maxZIndex; + hov.depth = canvas.minDepth; + hov.selection.entity = e.h; + hoverList.Add(&hov); } } // The constraints and faces happen only when nothing's in progress. - if(pending.operation == 0) { + if(pending.operation == Pending::NONE) { // Constraints - for(i = 0; i < SK.constraint.n; i++) { - d = SK.constraint.elem[i].GetDistance(mp); - if(d < SELECTION_RADIUS && d < dmin) { - s = {}; - s.constraint = SK.constraint.elem[i].h; - dmin = d; + for(Constraint &c : SK.constraint) { + if(canvas.Pick([&]{ c.Draw(Constraint::DrawAs::DEFAULT, &canvas); })) { + Hover hov = {}; + hov.distance = canvas.minDistance; + hov.zIndex = canvas.maxZIndex; + hov.selection.constraint = c.h; + hoverList.Add(&hov); } } + } + + std::sort(hoverList.begin(), hoverList.end(), + [](const Hover &a, const Hover &b) { + if(a.zIndex == b.zIndex) return a.distance < b.distance; + return a.zIndex > b.zIndex; + }); + sel = ChooseFromHoverToSelect(); + if(pending.operation == Pending::NONE) { // Faces, from the triangle mesh; these are lowest priority - if(s.constraint.v == 0 && s.entity.v == 0 && showShaded && showFaces) { + if(sel.constraint.v == 0 && sel.entity.v == 0 && showShaded && showFaces) { Group *g = SK.GetGroup(activeGroup); SMesh *m = &(g->displayMesh); uint32_t v = m->FirstIntersectionWith(mp); if(v) { - s.entity.v = v; + sel.entity.v = v; } } } - if(!s.Equals(&hover)) { - hover = s; - PaintGraphics(); + canvas.Clear(); + + if(!sel.Equals(&hover)) { + hover = sel; + Invalidate(); } } @@ -456,7 +560,7 @@ Vector GraphicsWindow::UnProjectPoint3(Vector p) { return orig; } -void GraphicsWindow::NormalizeProjectionVectors(void) { +void GraphicsWindow::NormalizeProjectionVectors() { if(projRight.Magnitude() < LENGTH_EPS) { projRight = Vector::From(1, 0, 0); } @@ -473,358 +577,252 @@ void GraphicsWindow::NormalizeProjectionVectors(void) { projRight = projRight.WithMagnitude(1); } -Vector GraphicsWindow::VectorFromProjs(Vector rightUpForward) { - Vector n = projRight.Cross(projUp); +void GraphicsWindow::DrawSnapGrid(Canvas *canvas) { + if(!LockedInWorkplane()) return; - Vector r = (projRight.ScaledBy(rightUpForward.x)); - r = r.Plus(projUp.ScaledBy(rightUpForward.y)); - r = r.Plus(n.ScaledBy(rightUpForward.z)); - return r; -} + const Camera &camera = canvas->GetCamera(); + double width = camera.width, + height = camera.height; -void GraphicsWindow::Paint(void) { - int i; - havePainted = true; - - int w, h; - GetGraphicsWindowSize(&w, &h); - width = w; height = h; - glViewport(0, 0, w, h); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - glScaled(scale*2.0/w, scale*2.0/h, scale*1.0/30000); - - double mat[16]; - // Last thing before display is to apply the perspective - double clp = SS.CameraTangent()*scale; - MakeMatrix(mat, 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, clp, 1); - glMultMatrixd(mat); - // Before that, we apply the rotation + hEntity he = ActiveWorkplane(); + EntityBase *wrkpl = SK.GetEntity(he), + *norm = wrkpl->Normal(); Vector n = projUp.Cross(projRight); - MakeMatrix(mat, projRight.x, projRight.y, projRight.z, 0, - projUp.x, projUp.y, projUp.z, 0, - n.x, n.y, n.z, 0, - 0, 0, 0, 1); - glMultMatrixd(mat); - // And before that, the translation - MakeMatrix(mat, 1, 0, 0, offset.x, - 0, 1, 0, offset.y, - 0, 0, 1, offset.z, - 0, 0, 0, 1); - glMultMatrixd(mat); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glShadeModel(GL_SMOOTH); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); - glEnable(GL_LINE_SMOOTH); - // don't enable GL_POLYGON_SMOOTH; that looks ugly on some graphics cards, - // drawn with leaks in the mesh - glEnable(GL_POLYGON_OFFSET_LINE); - glEnable(GL_POLYGON_OFFSET_FILL); - glEnable(GL_DEPTH_TEST); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - glEnable(GL_NORMALIZE); - - // At the same depth, we want later lines drawn over earlier. - glDepthFunc(GL_LEQUAL); - - if(SS.ActiveGroupsOkay()) { - glClearColor(SS.backgroundColor.redF(), - SS.backgroundColor.greenF(), - SS.backgroundColor.blueF(), 1.0f); - } else { - // Draw a different background whenever we're having solve problems. - RgbaColor rgb = Style::Color(Style::DRAW_ERROR); - glClearColor(0.4f*rgb.redF(), 0.4f*rgb.greenF(), 0.4f*rgb.blueF(), 1.0f); - // And show the text window, which has info to debug it - ForceTextWindowShown(); + Vector wu, wv, wn, wp; + wp = SK.GetEntity(wrkpl->point[0])->PointGetNum(); + wu = norm->NormalU(); + wv = norm->NormalV(); + wn = norm->NormalN(); + + double g = SS.gridSpacing; + + double umin = VERY_POSITIVE, umax = VERY_NEGATIVE, + vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE; + int a; + for(a = 0; a < 4; a++) { + // Ideally, we would just do +/- half the width and height; but + // allow some extra slop for rounding. + Vector horiz = projRight.ScaledBy((0.6*width)/scale + 2*g), + vert = projUp. ScaledBy((0.6*height)/scale + 2*g); + if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1); + if(a == 1 || a == 3) vert = vert. ScaledBy(-1); + Vector tp = horiz.Plus(vert).Minus(offset); + + // Project the point into our grid plane, normal to the screen + // (not to the grid plane). If the plane is on edge then this is + // impossible so don't try to draw the grid. + bool parallel; + Vector tpp = Vector::AtIntersectionOfPlaneAndLine( + wn, wn.Dot(wp), + tp, tp.Plus(n), + ¶llel); + if(parallel) return; + + tpp = tpp.Minus(wp); + double uu = tpp.Dot(wu), + vv = tpp.Dot(wv); + + umin = min(uu, umin); + umax = max(uu, umax); + vmin = min(vv, vmin); + vmax = max(vv, vmax); } - glClear(GL_COLOR_BUFFER_BIT); - glClearDepth(1.0); - glClear(GL_DEPTH_BUFFER_BIT); - - if(SS.bgImage.fromFile) { - // If a background image is loaded, then we draw it now as a texture. - // This handles the resizing for us nicely. - glBindTexture(GL_TEXTURE_2D, TEXTURE_BACKGROUND_IMG); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, - SS.bgImage.rw, SS.bgImage.rh, - 0, - GL_RGB, GL_UNSIGNED_BYTE, - SS.bgImage.fromFile); - - double tw = ((double)SS.bgImage.w) / SS.bgImage.rw, - th = ((double)SS.bgImage.h) / SS.bgImage.rh; - - double mmw = SS.bgImage.w / SS.bgImage.scale, - mmh = SS.bgImage.h / SS.bgImage.scale; - - Vector origin = SS.bgImage.origin; - origin = origin.DotInToCsys(projRight, projUp, n); - // Place the depth of our origin at the point that corresponds to - // w = 1, so that it's unaffected by perspective. - origin.z = (offset.ScaledBy(-1)).Dot(n); - origin = origin.ScaleOutOfCsys(projRight, projUp, n); - - // Place the background at the very back of the Z order, though, by - // mucking with the depth range. - glDepthRange(1, 1); - glEnable(GL_TEXTURE_2D); - glBegin(GL_QUADS); - glTexCoord2d(0, 0); - ssglVertex3v(origin); - - glTexCoord2d(0, th); - ssglVertex3v(origin.Plus(projUp.ScaledBy(mmh))); - - glTexCoord2d(tw, th); - ssglVertex3v(origin.Plus(projRight.ScaledBy(mmw).Plus( - projUp. ScaledBy(mmh)))); - - glTexCoord2d(tw, 0); - ssglVertex3v(origin.Plus(projRight.ScaledBy(mmw))); - glEnd(); - glDisable(GL_TEXTURE_2D); - } - ssglDepthRangeOffset(0); - // Nasty case when we're reloading the linked files; could be that - // we get an error, so a dialog pops up, and a message loop starts, and - // we have to get called to paint ourselves. If the sketch is screwed - // up, then we could trigger an oops trying to draw. - if(!SS.allConsistent) return; + int i, j, i0, i1, j0, j1; - // Let's use two lights, at the user-specified locations - GLfloat f; - glEnable(GL_LIGHT0); - f = (GLfloat)SS.lightIntensity[0]; - GLfloat li0[] = { f, f, f, 1.0f }; - glLightfv(GL_LIGHT0, GL_DIFFUSE, li0); - glLightfv(GL_LIGHT0, GL_SPECULAR, li0); - - glEnable(GL_LIGHT1); - f = (GLfloat)SS.lightIntensity[1]; - GLfloat li1[] = { f, f, f, 1.0f }; - glLightfv(GL_LIGHT1, GL_DIFFUSE, li1); - glLightfv(GL_LIGHT1, GL_SPECULAR, li1); - - Vector ld; - ld = VectorFromProjs(SS.lightDir[0]); - GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 }; - glLightfv(GL_LIGHT0, GL_POSITION, ld0); - ld = VectorFromProjs(SS.lightDir[1]); - GLfloat ld1[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 }; - glLightfv(GL_LIGHT1, GL_POSITION, ld1); - - GLfloat ambient[4] = { (float)SS.ambientIntensity, - (float)SS.ambientIntensity, - (float)SS.ambientIntensity, 1 }; - glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); - - ssglUnlockColor(); - - if(showSnapGrid && LockedInWorkplane()) { - hEntity he = ActiveWorkplane(); - EntityBase *wrkpl = SK.GetEntity(he), - *norm = wrkpl->Normal(); - Vector wu, wv, wn, wp; - wp = SK.GetEntity(wrkpl->point[0])->PointGetNum(); - wu = norm->NormalU(); - wv = norm->NormalV(); - wn = norm->NormalN(); - - double g = SS.gridSpacing; - - double umin = VERY_POSITIVE, umax = VERY_NEGATIVE, - vmin = VERY_POSITIVE, vmax = VERY_NEGATIVE; - int a; - for(a = 0; a < 4; a++) { - // Ideally, we would just do +/- half the width and height; but - // allow some extra slop for rounding. - Vector horiz = projRight.ScaledBy((0.6*width)/scale + 2*g), - vert = projUp. ScaledBy((0.6*height)/scale + 2*g); - if(a == 2 || a == 3) horiz = horiz.ScaledBy(-1); - if(a == 1 || a == 3) vert = vert. ScaledBy(-1); - Vector tp = horiz.Plus(vert).Minus(offset); - - // Project the point into our grid plane, normal to the screen - // (not to the grid plane). If the plane is on edge then this is - // impossible so don't try to draw the grid. - bool parallel; - Vector tpp = Vector::AtIntersectionOfPlaneAndLine( - wn, wn.Dot(wp), - tp, tp.Plus(n), - ¶llel); - if(parallel) goto nogrid; - - tpp = tpp.Minus(wp); - double uu = tpp.Dot(wu), - vv = tpp.Dot(wv); - - umin = min(uu, umin); - umax = max(uu, umax); - vmin = min(vv, vmin); - vmax = max(vv, vmax); - } + i0 = (int)(umin / g); + i1 = (int)(umax / g); + j0 = (int)(vmin / g); + j1 = (int)(vmax / g); - int i, j, i0, i1, j0, j1; + if(i0 > i1 || i1 - i0 > 400) return; + if(j0 > j1 || j1 - j0 > 400) return; - i0 = (int)(umin / g); - i1 = (int)(umax / g); - j0 = (int)(vmin / g); - j1 = (int)(vmax / g); + Canvas::Stroke stroke = {}; + stroke.layer = Canvas::Layer::BACK; + stroke.color = Style::Color(Style::DATUM).WithAlpha(75); + stroke.unit = Canvas::Unit::PX; + stroke.width = 1.0f; + Canvas::hStroke hcs = canvas->GetStroke(stroke); - if(i0 > i1 || i1 - i0 > 400) goto nogrid; - if(j0 > j1 || j1 - j0 > 400) goto nogrid; + for(i = i0 + 1; i < i1; i++) { + canvas->DrawLine(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g)), + wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g)), + hcs); + } + for(j = j0 + 1; j < j1; j++) { + canvas->DrawLine(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g)), + wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g)), + hcs); + } +} - ssglLineWidth(1); - ssglColorRGBa(Style::Color(Style::DATUM), 0.3); - glBegin(GL_LINES); - for(i = i0 + 1; i < i1; i++) { - ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j0*g))); - ssglVertex3v(wp.Plus(wu.ScaledBy(i*g)).Plus(wv.ScaledBy(j1*g))); - } - for(j = j0 + 1; j < j1; j++) { - ssglVertex3v(wp.Plus(wu.ScaledBy(i0*g)).Plus(wv.ScaledBy(j*g))); - ssglVertex3v(wp.Plus(wu.ScaledBy(i1*g)).Plus(wv.ScaledBy(j*g))); +void GraphicsWindow::DrawEntities(Canvas *canvas, bool persistent) { + for(Entity &e : SK.entity) { + if(persistent == (e.IsNormal() || e.IsWorkplane())) continue; + switch(SS.GW.drawOccludedAs) { + case DrawOccludedAs::VISIBLE: + e.Draw(Entity::DrawAs::OVERLAY, canvas); + break; + + case DrawOccludedAs::STIPPLED: + e.Draw(Entity::DrawAs::HIDDEN, canvas); + /* fallthrough */ + case DrawOccludedAs::INVISIBLE: + e.Draw(Entity::DrawAs::DEFAULT, canvas); + break; } - glEnd(); - - // Clear the depth buffer, so that the grid is at the very back of - // the Z order. - glClear(GL_DEPTH_BUFFER_BIT); -nogrid:; } +} +void GraphicsWindow::DrawPersistent(Canvas *canvas) { // Draw the active group; this does stuff like the mesh and edges. - (SK.GetGroup(activeGroup))->Draw(); + SK.GetGroup(activeGroup)->Draw(canvas); - // Now draw the entities. - if(SS.GW.showHdnLines) { - ssglDepthRangeOffset(2); - glDepthFunc(GL_GREATER); - Entity::DrawAll(/*drawAsHidden=*/true); - glDepthFunc(GL_LEQUAL); - } - ssglDepthRangeOffset(0); - Entity::DrawAll(/*drawAsHidden=*/false); + // Now draw the entities that don't change with viewport. + DrawEntities(canvas, /*persistent=*/true); // Draw filled paths in all groups, when those filled paths were requested // specially by assigning a style with a fill color, or when the filled // paths are just being filled by default. This should go last, to make // the transparency work. - for(i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); + for(hGroup hg : SK.groupOrder) { + Group *g = SK.GetGroup(hg); if(!(g->IsVisible())) continue; - g->DrawFilledPaths(); + g->DrawFilledPaths(canvas); } +} +void GraphicsWindow::Draw(Canvas *canvas) { + const Camera &camera = canvas->GetCamera(); + + // Nasty case when we're reloading the linked files; could be that + // we get an error, so a dialog pops up, and a message loop starts, and + // we have to get called to paint ourselves. If the sketch is screwed + // up, then we could trigger an oops trying to draw. + if(!SS.allConsistent) return; + + if(showSnapGrid) DrawSnapGrid(canvas); + + // Draw all the things that don't change when we rotate. + if(persistentCanvas != NULL) { + if(persistentDirty) { + persistentDirty = false; + + persistentCanvas->Clear(); + DrawPersistent(&*persistentCanvas); + persistentCanvas->Finalize(); + } + + persistentCanvas->Draw(); + } else { + DrawPersistent(canvas); + } + + // Draw the entities that do change with viewport. + DrawEntities(canvas, /*persistent=*/false); + + // Draw the polygon errors. + if(SS.checkClosedContour) { + SK.GetGroup(activeGroup)->DrawPolyError(canvas); + } - glDisable(GL_DEPTH_TEST); // Draw the constraints - for(i = 0; i < SK.constraint.n; i++) { - SK.constraint.elem[i].Draw(); + for(Constraint &c : SK.constraint) { + c.Draw(Constraint::DrawAs::DEFAULT, canvas); } - // Draw the "pending" constraint, i.e. a constraint that would be - // placed on a line that is almost horizontal or vertical - if(SS.GW.pending.operation == DRAGGING_NEW_LINE_POINT) { - if(SS.GW.pending.suggestion != GraphicsWindow::SUGGESTED_NONE) { - Constraint c = {}; - c.group = SS.GW.activeGroup; - c.workplane = SS.GW.ActiveWorkplane(); - c.type = SS.GW.pending.suggestion; - c.ptA = Entity::NO_ENTITY; - c.ptB = Entity::NO_ENTITY; - c.entityA = SS.GW.pending.request.entity(0); - c.entityB = Entity::NO_ENTITY; - c.other = false; - c.other2 = false; - // Only draw. - c.Draw(); + // Draw areas + if(SS.showContourAreas) { + for(hGroup hg : SK.groupOrder) { + Group *g = SK.GetGroup(hg); + if(g->h != activeGroup) continue; + if(!(g->IsVisible())) continue; + g->DrawContourAreaLabels(canvas); } } - // Draw the traced path, if one exists - ssglLineWidth(Style::Width(Style::ANALYZE)); - ssglColorRGB(Style::Color(Style::ANALYZE)); - SContour *sc = &(SS.traced.path); - glBegin(GL_LINE_STRIP); - for(i = 0; i < sc->l.n; i++) { - ssglVertex3v(sc->l.elem[i].p); + // Draw the "pending" constraint, i.e. a constraint that would be + // placed on a line that is almost horizontal or vertical. + if(SS.GW.pending.operation == Pending::DRAGGING_NEW_LINE_POINT && + SS.GW.pending.hasSuggestion) { + Constraint c = {}; + c.group = SS.GW.activeGroup; + c.workplane = SS.GW.ActiveWorkplane(); + c.type = SS.GW.pending.suggestion; + c.entityA = SS.GW.pending.request.entity(0); + c.Draw(Constraint::DrawAs::DEFAULT, canvas); } - glEnd(); + + Canvas::Stroke strokeAnalyze = Style::Stroke(Style::ANALYZE); + strokeAnalyze.layer = Canvas::Layer::FRONT; + Canvas::hStroke hcsAnalyze = canvas->GetStroke(strokeAnalyze); + + // Draw the traced path, if one exists + SEdgeList tracedEdges = {}; + SS.traced.path.MakeEdgesInto(&tracedEdges); + canvas->DrawEdges(tracedEdges, hcsAnalyze); + tracedEdges.Clear(); + + Canvas::Stroke strokeError = Style::Stroke(Style::DRAW_ERROR); + strokeError.layer = Canvas::Layer::FRONT; + strokeError.width = 12; + Canvas::hStroke hcsError = canvas->GetStroke(strokeError); // And the naked edges, if the user did Analyze -> Show Naked Edges. - ssglDrawEdges(&(SS.nakedEdges), true, { Style::DRAW_ERROR }); + canvas->DrawEdges(SS.nakedEdges, hcsError); // Then redraw whatever the mouse is hovering over, highlighted. - glDisable(GL_DEPTH_TEST); - ssglLockColorTo(Style::Color(Style::HOVERED)); - hover.Draw(); + hover.Draw(/*isHovered=*/true, canvas); + SK.GetGroup(activeGroup)->DrawMesh(Group::DrawMeshAs::HOVERED, canvas); // And finally draw the selection, same mechanism. - ssglLockColorTo(Style::Color(Style::SELECTED)); for(Selection *s = selection.First(); s; s = selection.NextAfter(s)) { - s->Draw(); + s->Draw(/*isHovered=*/false, canvas); } + SK.GetGroup(activeGroup)->DrawMesh(Group::DrawMeshAs::SELECTED, canvas); - ssglUnlockColor(); - - // If a marquee selection is in progress, then draw the selection - // rectangle, as an outline and a transparent fill. - if(pending.operation == DRAGGING_MARQUEE) { - Point2d begin = ProjectPoint(orig.marqueePoint); - double xmin = min(orig.mouse.x, begin.x), - xmax = max(orig.mouse.x, begin.x), - ymin = min(orig.mouse.y, begin.y), - ymax = max(orig.mouse.y, begin.y); - - Vector tl = UnProjectPoint(Point2d::From(xmin, ymin)), - tr = UnProjectPoint(Point2d::From(xmax, ymin)), - br = UnProjectPoint(Point2d::From(xmax, ymax)), - bl = UnProjectPoint(Point2d::From(xmin, ymax)); - - ssglLineWidth((GLfloat)1.3); - ssglColorRGB(Style::Color(Style::HOVERED)); - glBegin(GL_LINE_LOOP); - ssglVertex3v(tl); - ssglVertex3v(tr); - ssglVertex3v(br); - ssglVertex3v(bl); - glEnd(); - ssglColorRGBa(Style::Color(Style::HOVERED), 0.10); - glBegin(GL_QUADS); - ssglVertex3v(tl); - ssglVertex3v(tr); - ssglVertex3v(br); - ssglVertex3v(bl); - glEnd(); - } + Canvas::Stroke strokeDatum = Style::Stroke(Style::DATUM); + strokeDatum.unit = Canvas::Unit::PX; + strokeDatum.layer = Canvas::Layer::FRONT; + strokeDatum.width = 1; + Canvas::hStroke hcsDatum = canvas->GetStroke(strokeDatum); // An extra line, used to indicate the origin when rotating within the // plane of the monitor. if(SS.extraLine.draw) { - ssglLineWidth(1); - ssglLockColorTo(Style::Color(Style::DATUM)); - glBegin(GL_LINES); - ssglVertex3v(SS.extraLine.ptA); - ssglVertex3v(SS.extraLine.ptB); - glEnd(); + canvas->DrawLine(SS.extraLine.ptA, SS.extraLine.ptB, hcsDatum); + } + + if(SS.centerOfMass.draw && !SS.centerOfMass.dirty) { + Vector p = SS.centerOfMass.position; + Vector u = camera.projRight; + Vector v = camera.projUp; + + const double size = 10.0; + const int subdiv = 16; + double h = Style::DefaultTextHeight() / camera.scale; + std::string s = + SS.MmToStringSI(p.x) + ", " + + SS.MmToStringSI(p.y) + ", " + + SS.MmToStringSI(p.z); + canvas->DrawVectorText(s.c_str(), h, + p.Plus(u.ScaledBy((size + 5.0)/scale)).Minus(v.ScaledBy(h / 2.0)), + u, v, hcsDatum); + u = u.WithMagnitude(size / scale); + v = v.WithMagnitude(size / scale); + + canvas->DrawLine(p.Minus(u), p.Plus(u), hcsDatum); + canvas->DrawLine(p.Minus(v), p.Plus(v), hcsDatum); + Vector prev; + for(int i = 0; i <= subdiv; i++) { + double a = (double)i / subdiv * 2.0 * PI; + Vector point = p.Plus(u.ScaledBy(cos(a))).Plus(v.ScaledBy(sin(a))); + if(i > 0) { + canvas->DrawLine(point, prev, hcsDatum); + } + prev = point; + } } // A note to indicate the origin in the just-exported file. @@ -835,38 +833,106 @@ nogrid:; u = SS.justExportedInfo.u, v = SS.justExportedInfo.v; } else { - p = SS.GW.offset.ScaledBy(-1); - u = SS.GW.projRight; - v = SS.GW.projUp; + p = camera.offset.ScaledBy(-1); + u = camera.projRight; + v = camera.projUp; } + canvas->DrawVectorText("previewing exported geometry; press Esc to return", + Style::DefaultTextHeight() / camera.scale, + p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)), u, v, + hcsDatum); - ssglColorRGB(Style::Color(Style::DATUM)); + if(SS.justExportedInfo.showOrigin) { + Vector um = p.Plus(u.WithMagnitude(-15/scale)), + up = p.Plus(u.WithMagnitude(30/scale)), + vm = p.Plus(v.WithMagnitude(-15/scale)), + vp = p.Plus(v.WithMagnitude(30/scale)); + canvas->DrawLine(um, up, hcsDatum); + canvas->DrawLine(vm, vp, hcsDatum); + canvas->DrawVectorText("(x, y) = (0, 0) for file just exported", + Style::DefaultTextHeight() / camera.scale, + p.Plus(u.ScaledBy(40/scale)).Plus( + v.ScaledBy(-(Style::DefaultTextHeight())/scale)), u, v, + hcsDatum); + } + } +} - ssglWriteText("previewing exported geometry; press Esc to return", - Style::DefaultTextHeight(), - p.Plus(u.ScaledBy(10/scale)).Plus(v.ScaledBy(10/scale)), - u, v, NULL, NULL); +void GraphicsWindow::Paint() { + ssassert(window != NULL && canvas != NULL, + "Cannot paint without window and canvas"); - if(SS.justExportedInfo.showOrigin) { - ssglLineWidth(1.5); - glBegin(GL_LINES); - ssglVertex3v(p.Plus(u.WithMagnitude(-15/scale))); - ssglVertex3v(p.Plus(u.WithMagnitude(30/scale))); - ssglVertex3v(p.Plus(v.WithMagnitude(-15/scale))); - ssglVertex3v(p.Plus(v.WithMagnitude(30/scale))); - glEnd(); - - ssglWriteText("(x, y) = (0, 0) for file just exported", - Style::DefaultTextHeight(), - p.Plus(u.ScaledBy(40/scale)).Plus( - v.ScaledBy(-(Style::DefaultTextHeight())/scale)), - u, v, NULL, NULL); + havePainted = true; + + Camera camera = GetCamera(); + Lighting lighting = GetLighting(); + + if(!SS.ActiveGroupsOkay()) { + // Draw a different background whenever we're having solve problems. + RgbaColor bgColor = Style::Color(Style::DRAW_ERROR); + bgColor = RgbaColor::FromFloat(0.4f*bgColor.redF(), + 0.4f*bgColor.greenF(), + 0.4f*bgColor.blueF()); + lighting.backgroundColor = bgColor; + // And show the text window, which has info to debug it + ForceTextWindowShown(); + } + + canvas->SetLighting(lighting); + canvas->SetCamera(camera); + canvas->StartFrame(); + + // Draw the 3d objects. + Draw(canvas.get()); + canvas->FlushFrame(); + + // Draw the 2d UI overlay. + camera.LoadIdentity(); + camera.offset.x = -(double)camera.width / 2.0; + camera.offset.y = -(double)camera.height / 2.0; + canvas->SetCamera(camera); + + UiCanvas uiCanvas = {}; + uiCanvas.canvas = canvas; + + // If a marquee selection is in progress, then draw the selection + // rectangle, as an outline and a transparent fill. + if(pending.operation == Pending::DRAGGING_MARQUEE) { + Point2d begin = ProjectPoint(orig.marqueePoint); + uiCanvas.DrawRect((int)orig.mouse.x + (int)camera.width / 2, + (int)begin.x + (int)camera.width / 2, + (int)orig.mouse.y + (int)camera.height / 2, + (int)begin.y + (int)camera.height / 2, + /*fillColor=*/Style::Color(Style::HOVERED).WithAlpha(25), + /*outlineColor=*/Style::Color(Style::HOVERED)); + } + + // If we've had a screenshot requested, take it now, before the UI is overlaid. + if(!SS.screenshotFile.IsEmpty()) { + FILE *f = OpenFile(SS.screenshotFile, "wb"); + if(!f || !canvas->ReadFrame()->WritePng(f, /*flip=*/true)) { + Error("Couldn't write to '%s'", SS.screenshotFile.raw.c_str()); } + if(f) fclose(f); + SS.screenshotFile.Clear(); } // And finally the toolbar. if(SS.showToolbar) { - ToolbarDraw(); + canvas->SetCamera(camera); + ToolbarDraw(&uiCanvas); } + + canvas->FlushFrame(); + canvas->FinishFrame(); + canvas->Clear(); } +void GraphicsWindow::Invalidate(bool clearPersistent) { + if(window) { + if(clearPersistent) { + persistentDirty = true; + } + window->Invalidate(); + } +} diff --git a/src/drawconstraint.cpp b/src/drawconstraint.cpp index 31139bc..2cdb9af 100644 --- a/src/drawconstraint.cpp +++ b/src/drawconstraint.cpp @@ -8,59 +8,23 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -void Constraint::LineDrawOrGetDistance(Vector a, Vector b) { - if(dogd.drawing) { - hStyle hs = GetStyle(); - - if(dogd.sel) { - dogd.sel->AddEdge(a, b, hs.v); - } else { - if(Style::Width(hs) >= 3.0) { - ssglFatLine(a, b, Style::Width(hs) / SS.GW.scale); - } else { - glBegin(GL_LINE_STRIP); - ssglVertex3v(a); - ssglVertex3v(b); - glEnd(); - } - } - } else { - Point2d ap = SS.GW.ProjectPoint(a); - Point2d bp = SS.GW.ProjectPoint(b); - - double d = dogd.mp.DistanceToLine(ap, bp.Minus(ap), true); - dogd.dmin = min(dogd.dmin, d); - } - dogd.refp = (a.Plus(b)).ScaledBy(0.5); -} - -static void LineCallback(void *fndata, Vector a, Vector b) -{ - Constraint *c = (Constraint *)fndata; - c->LineDrawOrGetDistance(a, b); -} - -std::string Constraint::Label(void) { +std::string Constraint::Label() const { std::string result; - if(type == ANGLE) { - if(valA == floor(valA)) { - result = ssprintf("%.0f°", valA); - } else { - result = ssprintf("%.2f°", valA); - } - } else if(type == LENGTH_RATIO) { + if(type == Type::ANGLE) { + result = SS.DegreeToString(valA) + "°"; + } else if(type == Type::LENGTH_RATIO) { result = ssprintf("%.3f:1", valA); - } else if(type == COMMENT) { + } else if(type == Type::COMMENT) { result = comment; - } else if(type == DIAMETER) { + } else if(type == Type::DIAMETER) { if(!other) { - result = "⌀" + SS.MmToString(valA); + result = "⌀" + SS.MmToStringSI(valA); } else { - result = "R" + SS.MmToString(valA / 2); + result = "R" + SS.MmToStringSI(valA / 2); } } else { // valA has units of distance - result = SS.MmToString(fabs(valA)); + result = SS.MmToStringSI(fabs(valA)); } if(reference) { result += " REF"; @@ -68,18 +32,35 @@ std::string Constraint::Label(void) { return result; } -void Constraint::DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu) { - hStyle hs = GetStyle(); - double th = Style::TextHeight(hs); +void Constraint::DoLine(Canvas *canvas, Canvas::hStroke hcs, Vector a, Vector b) { + const Camera &camera = canvas->GetCamera(); + + a = camera.AlignToPixelGrid(a); + b = camera.AlignToPixelGrid(b); + canvas->DrawLine(a, b, hcs); +} + +void Constraint::DoStippledLine(Canvas *canvas, Canvas::hStroke hcs, Vector a, Vector b) { + Canvas::Stroke strokeStippled = *canvas->strokes.FindById(hcs); + strokeStippled.stipplePattern = StipplePattern::SHORT_DASH; + strokeStippled.stippleScale = 4.0; + Canvas::hStroke hcsStippled = canvas->GetStroke(strokeStippled); + DoLine(canvas, hcsStippled, a, b); +} + +void Constraint::DoLabel(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector *labelPos, Vector gr, Vector gu) { + const Camera &camera = canvas->GetCamera(); std::string s = Label(); - double swidth = ssglStrWidth(s, th), - sheight = ssglStrCapHeight(th); + double textHeight = Style::TextHeight(GetStyle()) / camera.scale; + double swidth = VectorFont::Builtin()->GetWidth(textHeight, s), + sheight = VectorFont::Builtin()->GetCapHeight(textHeight); // By default, the reference is from the center; but the style could // specify otherwise if one is present, and it could also specify a // rotation. - if(type == COMMENT && disp.style.v) { + if(type == Type::COMMENT && disp.style.v) { Style *st = Style::Get(disp.style); // rotation first double rads = st->textAngle*PI/180; @@ -88,45 +69,30 @@ void Constraint::DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu) { gr = pr.ScaledBy( c).Plus(pu.ScaledBy(s)); gu = pr.ScaledBy(-s).Plus(pu.ScaledBy(c)); // then origin - int o = st->textOrigin; - if(o & Style::ORIGIN_LEFT) ref = ref.Plus(gr.WithMagnitude(swidth/2)); - if(o & Style::ORIGIN_RIGHT) ref = ref.Minus(gr.WithMagnitude(swidth/2)); - if(o & Style::ORIGIN_BOT) ref = ref.Plus(gu.WithMagnitude(sheight/2)); - if(o & Style::ORIGIN_TOP) ref = ref.Minus(gu.WithMagnitude(sheight/2)); + uint32_t o = (uint32_t)st->textOrigin; + if(o & (uint32_t)Style::TextOrigin::LEFT) ref = ref.Plus(gr.WithMagnitude(swidth/2)); + if(o & (uint32_t)Style::TextOrigin::RIGHT) ref = ref.Minus(gr.WithMagnitude(swidth/2)); + if(o & (uint32_t)Style::TextOrigin::BOT) ref = ref.Plus(gu.WithMagnitude(sheight/2)); + if(o & (uint32_t)Style::TextOrigin::TOP) ref = ref.Minus(gu.WithMagnitude(sheight/2)); } - if(labelPos) { - // labelPos is from the top left corner (for the text box used to - // edit things), but ref is from the center. - *labelPos = ref.Minus(gr.WithMagnitude(swidth/2)).Minus( - gu.WithMagnitude(sheight/2)); - } - - - if(dogd.drawing) { - ssglWriteTextRefCenter(s, th, ref, gr, gu, LineCallback, this); - } else { - double l = swidth/2 - sheight/2; - l = max(l, 5/SS.GW.scale); - Point2d a = SS.GW.ProjectPoint(ref.Minus(gr.WithMagnitude(l))); - Point2d b = SS.GW.ProjectPoint(ref.Plus (gr.WithMagnitude(l))); - double d = dogd.mp.DistanceToLine(a, b.Minus(a), true); - - dogd.dmin = min(dogd.dmin, d - (th / 2)); - dogd.refp = ref; - } + Vector o = ref.Minus(gr.WithMagnitude(swidth/2)).Minus( + gu.WithMagnitude(sheight/2)); + canvas->DrawVectorText(s, textHeight, o, gr.WithMagnitude(1), gu.WithMagnitude(1), hcs); + if(labelPos) *labelPos = o; } -void Constraint::StippledLine(Vector a, Vector b) { - glLineStipple(4, 0x5555); - glEnable(GL_LINE_STIPPLE); - LineDrawOrGetDistance(a, b); - glDisable(GL_LINE_STIPPLE); +void Constraint::DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs, + Vector *r, Vector n, Vector o) { + double d = r->DistanceToPlane(n, o); + Vector p = r->Minus(n.ScaledBy(d)); + DoStippledLine(canvas, hcs, p, *r); + *r = p; } -void Constraint::DoProjectedPoint(Vector *r) { +void Constraint::DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs, Vector *r) { Vector p = r->ProjectInto(workplane); - StippledLine(p, *r); + DoStippledLine(canvas, hcs, p, *r); *r = p; } @@ -138,21 +104,20 @@ void Constraint::DoProjectedPoint(Vector *r) { // the line to almost meet the box, and return either positive or negative, // depending whether that extension was from A or from B. //----------------------------------------------------------------------------- -int Constraint::DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool extend) { - hStyle hs = GetStyle(); - double th = Style::TextHeight(hs); - Vector gu = SS.GW.projUp.WithMagnitude(1), - gr = SS.GW.projRight.WithMagnitude(1); - - double pixels = 1.0 / SS.GW.scale; - std::string s = Label(); - double swidth = ssglStrWidth(s, th) + 4*pixels, - sheight = ssglStrCapHeight(th) + 8*pixels; - - return DoLineTrimmedAgainstBox(ref, a, b, extend, gr, gu, swidth, sheight); +int Constraint::DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool extend) { + const Camera &camera = canvas->GetCamera(); + double th = Style::TextHeight(GetStyle()) / camera.scale; + double pixels = 1.0 / camera.scale; + double swidth = VectorFont::Builtin()->GetWidth(th, Label()) + 8 * pixels, + sheight = VectorFont::Builtin()->GetCapHeight(th) + 8 * pixels; + Vector gu = camera.projUp.WithMagnitude(1), + gr = camera.projRight.WithMagnitude(1); + return DoLineTrimmedAgainstBox(canvas, hcs, ref, a, b, extend, gr, gu, swidth, sheight); } -int Constraint::DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool extend, +int Constraint::DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool extend, Vector gr, Vector gu, double swidth, double sheight) { struct { Vector n; @@ -183,27 +148,27 @@ int Constraint::DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool ext } if(j < 4) continue; - double t = (p.Minus(a)).DivPivoting(dl); + double t = (p.Minus(a)).DivProjected(dl); tmin = min(t, tmin); tmax = max(t, tmax); } // Both in range; so there's pieces of the line on both sides of the label box. if(tmin >= 0.0 && tmin <= 1.0 && tmax >= 0.0 && tmax <= 1.0) { - LineDrawOrGetDistance(a, a.Plus(dl.ScaledBy(tmin))); - LineDrawOrGetDistance(a.Plus(dl.ScaledBy(tmax)), b); + DoLine(canvas, hcs, a, a.Plus(dl.ScaledBy(tmin))); + DoLine(canvas, hcs, a.Plus(dl.ScaledBy(tmax)), b); return 0; } // Only one intersection in range; so the box is right on top of the endpoint if(tmin >= 0.0 && tmin <= 1.0) { - LineDrawOrGetDistance(a, a.Plus(dl.ScaledBy(tmin))); + DoLine(canvas, hcs, a, a.Plus(dl.ScaledBy(tmin))); return 0; } // Likewise. if(tmax >= 0.0 && tmax <= 1.0) { - LineDrawOrGetDistance(a.Plus(dl.ScaledBy(tmax)), b); + DoLine(canvas, hcs, a.Plus(dl.ScaledBy(tmax)), b); return 0; } @@ -213,13 +178,13 @@ int Constraint::DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool ext // and closer to b, positive means outside and closer to a. if(tmax < 0.0) { if(extend) a = a.Plus(dl.ScaledBy(tmax)); - LineDrawOrGetDistance(a, b); + DoLine(canvas, hcs, a, b); return 1; } if(tmin > 1.0) { if(extend) b = a.Plus(dl.ScaledBy(tmin)); - LineDrawOrGetDistance(a, b); + DoLine(canvas, hcs, a, b); return -1; } @@ -227,11 +192,12 @@ int Constraint::DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool ext return 0; } -void Constraint::DoArrow(Vector p, Vector dir, Vector n, double width, double angle, double da) { +void Constraint::DoArrow(Canvas *canvas, Canvas::hStroke hcs, + Vector p, Vector dir, Vector n, double width, double angle, double da) { dir = dir.WithMagnitude(width / cos(angle)); dir = dir.RotatedAbout(n, da); - LineDrawOrGetDistance(p, p.Plus(dir.RotatedAbout(n, angle))); - LineDrawOrGetDistance(p, p.Plus(dir.RotatedAbout(n, -angle))); + DoLine(canvas, hcs, p, p.Plus(dir.RotatedAbout(n, angle))); + DoLine(canvas, hcs, p, p.Plus(dir.RotatedAbout(n, -angle))); } //----------------------------------------------------------------------------- @@ -241,10 +207,12 @@ void Constraint::DoArrow(Vector p, Vector dir, Vector n, double width, double an // to the line AB, until the line between the extensions crosses ref (the // center of the label). //----------------------------------------------------------------------------- -void Constraint::DoLineWithArrows(Vector ref, Vector a, Vector b, +void Constraint::DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool onlyOneExt) { - double pixels = 1.0 / SS.GW.scale; + const Camera &camera = canvas->GetCamera(); + double pixels = 1.0 / camera.scale; Vector ab = a.Minus(b); Vector ar = a.Minus(ref); @@ -260,12 +228,12 @@ void Constraint::DoLineWithArrows(Vector ref, Vector a, Vector b, // Extension lines extend 10 pixels beyond where the arrows get // drawn (which is at the same offset perpendicular from AB as the // label). - LineDrawOrGetDistance(a, ae.Plus(out.WithMagnitude(10*pixels))); + DoLine(canvas, hcs, a, ae.Plus(out.WithMagnitude(10*pixels))); if(!onlyOneExt) { - LineDrawOrGetDistance(b, be.Plus(out.WithMagnitude(10*pixels))); + DoLine(canvas, hcs, b, be.Plus(out.WithMagnitude(10*pixels))); } - int within = DoLineTrimmedAgainstBox(ref, ae, be); + int within = DoLineTrimmedAgainstBox(canvas, hcs, ref, ae, be); // Arrow heads are 13 pixels long, with an 18 degree half-angle. double theta = 18*PI/180; @@ -274,23 +242,29 @@ void Constraint::DoLineWithArrows(Vector ref, Vector a, Vector b, if(within != 0) { arrow = arrow.ScaledBy(-1); Vector seg = (be.Minus(ae)).WithMagnitude(18*pixels); - if(within < 0) LineDrawOrGetDistance(ae, ae.Minus(seg)); - if(within > 0) LineDrawOrGetDistance(be, be.Plus(seg)); + if(within < 0) DoLine(canvas, hcs, ae, ae.Minus(seg)); + if(within > 0) DoLine(canvas, hcs, be, be.Plus(seg)); } - DoArrow(ae, arrow, n, 13.0 * pixels, theta, 0.0); - DoArrow(be, arrow.Negated(), n, 13.0 * pixels, theta, 0.0); + DoArrow(canvas, hcs, ae, arrow, n, 13.0 * pixels, theta, 0.0); + DoArrow(canvas, hcs, be, arrow.Negated(), n, 13.0 * pixels, theta, 0.0); } -void Constraint::DoEqualLenTicks(Vector a, Vector b, Vector gn) { +void Constraint::DoEqualLenTicks(Canvas *canvas, Canvas::hStroke hcs, + Vector a, Vector b, Vector gn, Vector *refp) { + const Camera &camera = canvas->GetCamera(); + Vector m = (a.ScaledBy(1.0/3)).Plus(b.ScaledBy(2.0/3)); + if(refp) *refp = m; Vector ab = a.Minus(b); - Vector n = (gn.Cross(ab)).WithMagnitude(10/SS.GW.scale); + Vector n = (gn.Cross(ab)).WithMagnitude(10/camera.scale); - LineDrawOrGetDistance(m.Minus(n), m.Plus(n)); + DoLine(canvas, hcs, m.Minus(n), m.Plus(n)); } -void Constraint::DoEqualRadiusTicks(hEntity he) { +void Constraint::DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs, + hEntity he, Vector *refp) { + const Camera &camera = canvas->GetCamera(); Entity *circ = SK.GetEntity(he); Vector center = SK.GetEntity(circ->point[0])->PointGetNum(); @@ -299,35 +273,38 @@ void Constraint::DoEqualRadiusTicks(hEntity he) { Vector u = q.RotationU(), v = q.RotationV(); double theta; - if(circ->type == Entity::CIRCLE) { + if(circ->type == Entity::Type::CIRCLE) { theta = PI/2; - } else if(circ->type == Entity::ARC_OF_CIRCLE) { + } else if(circ->type == Entity::Type::ARC_OF_CIRCLE) { double thetaa, thetab, dtheta; circ->ArcGetAngles(&thetaa, &thetab, &dtheta); theta = thetaa + dtheta/2; - } else oops(); + } else ssassert(false, "Unexpected entity type"); Vector d = u.ScaledBy(cos(theta)).Plus(v.ScaledBy(sin(theta))); d = d.ScaledBy(r); Vector p = center.Plus(d); - Vector tick = d.WithMagnitude(10/SS.GW.scale); - LineDrawOrGetDistance(p.Plus(tick), p.Minus(tick)); + if(refp) *refp = p; + Vector tick = d.WithMagnitude(10/camera.scale); + DoLine(canvas, hcs, p.Plus(tick), p.Minus(tick)); } -void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, - Vector offset, Vector *ref, bool trim) +void Constraint::DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs, + Vector a0, Vector da, Vector b0, Vector db, + Vector offset, Vector *ref, bool trim) { - Vector gr = SS.GW.projRight.ScaledBy(1.0); - Vector gu = SS.GW.projUp.ScaledBy(1.0); + const Camera &camera = canvas->GetCamera(); + double pixels = 1.0 / camera.scale; + Vector gr = camera.projRight.ScaledBy(1.0); + Vector gu = camera.projUp.ScaledBy(1.0); - if(workplane.v != Entity::FREE_IN_3D.v) { + if(workplane != Entity::FREE_IN_3D) { a0 = a0.ProjectInto(workplane); b0 = b0.ProjectInto(workplane); da = da.ProjectVectorInto(workplane); db = db.ProjectVectorInto(workplane); } - double px = 1.0 / SS.GW.scale; Vector a1 = a0.Plus(da); Vector b1 = b0.Plus(db); @@ -365,29 +342,27 @@ void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, double rda = rm.Dot(da), rdna = rm.Dot(dna); // Introduce minimal arc radius in pixels - double r = max(sqrt(rda*rda + rdna*rdna), 15.0 * px); + double r = max(sqrt(rda*rda + rdna*rdna), 15.0 * pixels); - hStyle hs = disp.style; - if(hs.v == 0) hs.v = Style::CONSTRAINT; - double th = Style::TextHeight(hs); - double swidth = ssglStrWidth(Label(), th) + 4.0 * px; - double sheight = ssglStrCapHeight(th) + 8.0 * px; + double th = Style::TextHeight(GetStyle()) / camera.scale; + double swidth = VectorFont::Builtin()->GetWidth(th, Label()) + 8*pixels, + sheight = VectorFont::Builtin()->GetCapHeight(th) + 6*pixels; double textR = sqrt(swidth * swidth + sheight * sheight) / 2.0; - *ref = pi.Plus(rm.WithMagnitude(std::max(rm.Magnitude(), 15 * px + textR))); + *ref = pi.Plus(rm.WithMagnitude(std::max(rm.Magnitude(), 15 * pixels + textR))); // Arrow points Vector apa = da. ScaledBy(r).Plus(pi); Vector apb = da. ScaledBy(r*cos(thetaf)).Plus( dna.ScaledBy(r*sin(thetaf))).Plus(pi); - double arrowW = 13 * px; + double arrowW = 13 * pixels; double arrowA = 18.0 * PI / 180.0; bool arrowVisible = apb.Minus(apa).Magnitude() > 2.5 * arrowW; // Arrow reversing indicator bool arrowRev = false; // The minimal extension length in angular representation - double extAngle = 18 * px / r; + double extAngle = 18 * pixels / r; // Arc additional angle double addAngle = 0.0; @@ -432,16 +407,17 @@ void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, dna.ScaledBy(r*sin(theta))).Plus(pi); if(i > 0) { if(trim) { - DoLineTrimmedAgainstBox(*ref, prev, p, false, gr, gu, swidth, sheight); + DoLineTrimmedAgainstBox(canvas, hcs, *ref, prev, p, + /*extend=*/false, gr, gu, swidth, sheight + 2*pixels); } else { - LineDrawOrGetDistance(prev, p); + DoLine(canvas, hcs, prev, p); } } prev = p; } - DoLineExtend(a0, a1, apa, 5.0 * px); - DoLineExtend(b0, b1, apb, 5.0 * px); + DoLineExtend(canvas, hcs, a0, a1, apa, 5.0 * pixels); + DoLineExtend(canvas, hcs, b0, b1, apb, 5.0 * pixels); // Draw arrows only when we have enough space. if(arrowVisible) { @@ -450,18 +426,21 @@ void Constraint::DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, dna = dna.ScaledBy(-1.0); angleCorr = -angleCorr; } - DoArrow(apa, dna, norm, arrowW, arrowA, angleCorr); - DoArrow(apb, dna, norm, arrowW, arrowA, thetaf + PI - angleCorr); + DoArrow(canvas, hcs, apa, dna, norm, arrowW, arrowA, angleCorr); + DoArrow(canvas, hcs, apb, dna, norm, arrowW, arrowA, thetaf + PI - angleCorr); } } else { // The lines are skew; no wonderful way to illustrate that. + *ref = a0.Plus(b0); *ref = (*ref).ScaledBy(0.5).Plus(disp.offset); gu = gu.WithMagnitude(1); + double textHeight = Style::TextHeight(GetStyle()) / camera.scale; Vector trans = - (*ref).Plus(gu.ScaledBy(-1.5*ssglStrCapHeight(Style::DefaultTextHeight()))); - ssglWriteTextRefCenter("angle between skew lines", Style::DefaultTextHeight(), - trans, gr.WithMagnitude(px), gu.WithMagnitude(px), LineCallback, this); + (*ref).Plus(gu.ScaledBy(-1.5*VectorFont::Builtin()->GetCapHeight(textHeight))); + canvas->DrawVectorText("angle between skew lines", textHeight, + trans, gr.WithMagnitude(1), gu.WithMagnitude(1), + hcs); } } @@ -473,7 +452,7 @@ bool Constraint::IsVisible() const { if(!(g->visible)) return false; // And likewise if the group is not the active group; except for comments // with an assigned style. - if(g->h.v != SS.GW.activeGroup.v && !(type == COMMENT && disp.style.v)) { + if(g->h != SS.GW.activeGroup && !(type == Type::COMMENT && disp.style.v)) { return false; } if(disp.style.v) { @@ -483,13 +462,14 @@ bool Constraint::IsVisible() const { return true; } -bool Constraint::DoLineExtend(Vector p0, Vector p1, Vector pt, double salient) { +bool Constraint::DoLineExtend(Canvas *canvas, Canvas::hStroke hcs, + Vector p0, Vector p1, Vector pt, double salient) { Vector dir = p1.Minus(p0); double k = dir.Dot(pt.Minus(p0)) / dir.Dot(dir); Vector ptOnLine = p0.Plus(dir.ScaledBy(k)); // Draw projection line. - LineDrawOrGetDistance(pt, ptOnLine); + DoLine(canvas, hcs, pt, ptOnLine); // Calculate salient direction. Vector sd = dir.WithMagnitude(1.0).ScaledBy(salient); @@ -508,62 +488,86 @@ bool Constraint::DoLineExtend(Vector p0, Vector p1, Vector pt, double salient) { } // Draw extension line. - LineDrawOrGetDistance(from, to); + DoLine(canvas, hcs, from, to); return true; } -void Constraint::DrawOrGetDistance(Vector *labelPos) { - - if(!IsVisible()) return; +void Constraint::DoLayout(DrawAs how, Canvas *canvas, + Vector *labelPos, std::vector *refs) { + if(!(how == DrawAs::HOVERED || how == DrawAs::SELECTED) && + !IsVisible()) return; // Unit vectors that describe our current view of the scene. One pixel // long, not one actual unit. - Vector gr = SS.GW.projRight.ScaledBy(1/SS.GW.scale); - Vector gu = SS.GW.projUp.ScaledBy(1/SS.GW.scale); - Vector gn = (gr.Cross(gu)).WithMagnitude(1/SS.GW.scale); + const Camera &camera = canvas->GetCamera(); + Vector gr = camera.projRight.ScaledBy(1/camera.scale); + Vector gu = camera.projUp.ScaledBy(1/camera.scale); + Vector gn = (gr.Cross(gu)).WithMagnitude(1/camera.scale); + + double textHeight = Style::TextHeight(GetStyle()) / camera.scale; + + RgbaColor color = {}; + switch(how) { + case DrawAs::DEFAULT: color = Style::Color(GetStyle()); break; + case DrawAs::HOVERED: color = Style::Color(Style::HOVERED); break; + case DrawAs::SELECTED: color = Style::Color(Style::SELECTED); break; + } + Canvas::Stroke stroke = Style::Stroke(GetStyle()); + stroke.layer = Canvas::Layer::FRONT; + stroke.color = color; + stroke.zIndex = 4; + Canvas::hStroke hcs = canvas->GetStroke(stroke); + + Canvas::Fill fill = {}; + fill.layer = Canvas::Layer::FRONT; + fill.color = color; + fill.zIndex = stroke.zIndex; + Canvas::hFill hcf = canvas->GetFill(fill); switch(type) { - case PT_PT_DISTANCE: { + case Type::PT_PT_DISTANCE: { Vector ap = SK.GetEntity(ptA)->PointGetNum(); Vector bp = SK.GetEntity(ptB)->PointGetNum(); - if(workplane.v != Entity::FREE_IN_3D.v) { - DoProjectedPoint(&ap); - DoProjectedPoint(&bp); + if(workplane != Entity::FREE_IN_3D) { + DoProjectedPoint(canvas, hcs, &ap); + DoProjectedPoint(canvas, hcs, &bp); } Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset); + if(refs) refs->push_back(ref); - DoLineWithArrows(ref, ap, bp, false); - DoLabel(ref, labelPos, gr, gu); - break; + DoLineWithArrows(canvas, hcs, ref, ap, bp, /*onlyOneExt=*/false); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); + return; } - case PROJ_PT_DISTANCE: { + case Type::PROJ_PT_DISTANCE: { Vector ap = SK.GetEntity(ptA)->PointGetNum(), bp = SK.GetEntity(ptB)->PointGetNum(), dp = (bp.Minus(ap)), pp = SK.GetEntity(entityA)->VectorGetNum(); Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(disp.offset); + if(refs) refs->push_back(ref); pp = pp.WithMagnitude(1); double d = dp.Dot(pp); Vector bpp = ap.Plus(pp.ScaledBy(d)); - StippledLine(ap, bpp); - StippledLine(bp, bpp); + DoStippledLine(canvas, hcs, ap, bpp); + DoStippledLine(canvas, hcs, bp, bpp); - DoLineWithArrows(ref, ap, bpp, false); - DoLabel(ref, labelPos, gr, gu); - break; + DoLineWithArrows(canvas, hcs, ref, ap, bpp, /*onlyOneExt=*/false); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); + return; } - case PT_FACE_DISTANCE: - case PT_PLANE_DISTANCE: { + case Type::PT_FACE_DISTANCE: + case Type::PT_PLANE_DISTANCE: { Vector pt = SK.GetEntity(ptA)->PointGetNum(); Entity *enta = SK.GetEntity(entityA); Vector n, p; - if(type == PT_PLANE_DISTANCE) { + if(type == Type::PT_PLANE_DISTANCE) { n = enta->Normal()->NormalN(); p = enta->WorkplaneGetOffset(); } else { @@ -575,67 +579,81 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { Vector closest = pt.Plus(n.WithMagnitude(d)); Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset); + if(refs) refs->push_back(ref); if(!pt.Equals(closest)) { - DoLineWithArrows(ref, pt, closest, true); + DoLineWithArrows(canvas, hcs, ref, pt, closest, /*onlyOneExt=*/true); } - DoLabel(ref, labelPos, gr, gu); - break; + DoLabel(canvas, hcs, ref, labelPos, gr, gu); + return; } - case PT_LINE_DISTANCE: { + case Type::PT_LINE_DISTANCE: { Vector pt = SK.GetEntity(ptA)->PointGetNum(); Entity *line = SK.GetEntity(entityA); Vector lA = SK.GetEntity(line->point[0])->PointGetNum(); Vector lB = SK.GetEntity(line->point[1])->PointGetNum(); Vector dl = lB.Minus(lA); - if(workplane.v != Entity::FREE_IN_3D.v) { + if(workplane != Entity::FREE_IN_3D) { lA = lA.ProjectInto(workplane); lB = lB.ProjectInto(workplane); - DoProjectedPoint(&pt); + DoProjectedPoint(canvas, hcs, &pt); } // Find the closest point on the line Vector closest = pt.ClosestPointOnLine(lA, dl); Vector ref = ((closest.Plus(pt)).ScaledBy(0.5)).Plus(disp.offset); - DoLabel(ref, labelPos, gr, gu); + if(refs) refs->push_back(ref); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); if(!pt.Equals(closest)) { - DoLineWithArrows(ref, pt, closest, true); + DoLineWithArrows(canvas, hcs, ref, pt, closest, /*onlyOneExt=*/true); + + // Draw projected point + Vector a = pt; + Vector b = closest; + Vector ab = a.Minus(b); + Vector ar = a.Minus(ref); + Vector n = ab.Cross(ar); + Vector out = ab.Cross(n).WithMagnitude(1); + out = out.ScaledBy(-out.Dot(ar)); + Vector be = b.Plus(out); + Vector np = lA.Minus(pt).Cross(lB.Minus(pt)).WithMagnitude(1.0); + DoProjectedPoint(canvas, hcs, &be, np, pt); // Extensions to line - double pixels = 1.0 / SS.GW.scale; + double pixels = 1.0 / camera.scale; Vector refClosest = ref.ClosestPointOnLine(lA, dl); double ddl = dl.Dot(dl); if(fabs(ddl) > LENGTH_EPS * LENGTH_EPS) { double t = refClosest.Minus(lA).Dot(dl) / ddl; if(t < 0.0) { - LineDrawOrGetDistance(refClosest.Minus(dl.WithMagnitude(10.0 * pixels)), lA); + DoLine(canvas, hcs, refClosest.Minus(dl.WithMagnitude(10.0 * pixels)), lA); } else if(t > 1.0) { - LineDrawOrGetDistance(refClosest.Plus(dl.WithMagnitude(10.0 * pixels)), lB); + DoLine(canvas, hcs, refClosest.Plus(dl.WithMagnitude(10.0 * pixels)), lB); } } } - if(workplane.v != Entity::FREE_IN_3D.v) { + if(workplane != Entity::FREE_IN_3D) { // Draw the projection marker from the closest point on the // projected line to the projected point on the real line. Vector lAB = (lA.Minus(lB)); - double t = (lA.Minus(closest)).DivPivoting(lAB); + double t = (lA.Minus(closest)).DivProjected(lAB); Vector lA = SK.GetEntity(line->point[0])->PointGetNum(); Vector lB = SK.GetEntity(line->point[1])->PointGetNum(); Vector c2 = (lA.ScaledBy(1-t)).Plus(lB.ScaledBy(t)); - DoProjectedPoint(&c2); + DoProjectedPoint(canvas, hcs, &c2); } - break; + return; } - case DIAMETER: { + case Type::DIAMETER: { Entity *circle = SK.GetEntity(entityA); Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum(); @@ -645,37 +663,25 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { Vector ref = center.Plus(disp.offset); // Force the label into the same plane as the circle. ref = ref.Minus(n.ScaledBy(n.Dot(ref) - n.Dot(center))); + if(refs) refs->push_back(ref); Vector mark = ref.Minus(center); mark = mark.WithMagnitude(mark.Magnitude()-r); - DoLineTrimmedAgainstBox(ref, ref, ref.Minus(mark)); + DoLineTrimmedAgainstBox(canvas, hcs, ref, ref, ref.Minus(mark)); Vector topLeft; - DoLabel(ref, &topLeft, gr, gu); + DoLabel(canvas, hcs, ref, &topLeft, gr, gu); if(labelPos) *labelPos = topLeft; - break; + return; } - case POINTS_COINCIDENT: { - if(!dogd.drawing) { - for(int i = 0; i < 2; i++) { - Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum(); - Point2d pp = SS.GW.ProjectPoint(p); - // The point is selected within a radius of 7, from the - // same center; so if the point is visible, then this - // constraint cannot be selected. But that's okay. - dogd.dmin = min(dogd.dmin, pp.DistanceTo(dogd.mp) - 3); - dogd.refp = p; - } - break; - } - - if(dogd.drawing) { + case Type::POINTS_COINCIDENT: { + if(how == DrawAs::DEFAULT) { // Let's adjust the color of this constraint to have the same // rough luma as the point color, so that the constraint does not // stand out in an ugly way. RgbaColor cd = Style::Color(Style::DATUM), - cc = Style::Color(Style::CONSTRAINT); + cc = Style::Color(Style::CONSTRAINT); // convert from 8-bit color to a vector Vector vd = Vector::From(cd.redF(), cd.greenF(), cd.blueF()), vc = Vector::From(cc.redF(), cc.greenF(), cc.blueF()); @@ -683,38 +689,40 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { // the datum color, maybe a bit dimmer vc = vc.WithMagnitude(vd.Magnitude()*0.9); // and set the color to that. - ssglColorRGB(RGBf(vc.x, vc.y, vc.z)); - - for(int a = 0; a < 2; a++) { - Vector r = SS.GW.projRight.ScaledBy((a+1)/SS.GW.scale); - Vector d = SS.GW.projUp.ScaledBy((2-a)/SS.GW.scale); - for(int i = 0; i < 2; i++) { - Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum(); - glBegin(GL_QUADS); - ssglVertex3v(p.Plus (r).Plus (d)); - ssglVertex3v(p.Plus (r).Minus(d)); - ssglVertex3v(p.Minus(r).Minus(d)); - ssglVertex3v(p.Minus(r).Plus (d)); - glEnd(); - } + fill.color = RGBf(vc.x, vc.y, vc.z); + hcf = canvas->GetFill(fill); + } + for(int a = 0; a < 2; a++) { + Vector r = camera.projRight.ScaledBy((a+1)/camera.scale); + Vector d = camera.projUp.ScaledBy((2-a)/camera.scale); + for(int i = 0; i < 2; i++) { + Vector p = SK.GetEntity(i == 0 ? ptA : ptB)-> PointGetNum(); + if(refs) refs->push_back(p); + canvas->DrawQuad(p.Plus (r).Plus (d), + p.Plus (r).Minus(d), + p.Minus(r).Minus(d), + p.Minus(r).Plus (d), + hcf); } + } - break; + return; } - case PT_ON_CIRCLE: - case PT_ON_LINE: - case PT_ON_FACE: - case PT_IN_PLANE: { - double s = 8/SS.GW.scale; + case Type::PT_ON_CIRCLE: + case Type::PT_ON_LINE: + case Type::PT_ON_FACE: + case Type::PT_IN_PLANE: { + double s = 8/camera.scale; Vector p = SK.GetEntity(ptA)->PointGetNum(); + if(refs) refs->push_back(p); Vector r, d; - if(type == PT_ON_FACE) { + if(type == Type::PT_ON_FACE) { Vector n = SK.GetEntity(entityA)->FaceGetNormalNum(); r = n.Normal(0); d = n.Normal(1); - } else if(type == PT_IN_PLANE) { + } else if(type == Type::PT_IN_PLANE) { EntityBase *n = SK.GetEntity(entityA)->Normal(); r = n->NormalU(); d = n->NormalV(); @@ -724,48 +732,50 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { s *= (6.0/8); // draw these a little smaller } r = r.WithMagnitude(s); d = d.WithMagnitude(s); - LineDrawOrGetDistance(p.Plus (r).Plus (d), p.Plus (r).Minus(d)); - LineDrawOrGetDistance(p.Plus (r).Minus(d), p.Minus(r).Minus(d)); - LineDrawOrGetDistance(p.Minus(r).Minus(d), p.Minus(r).Plus (d)); - LineDrawOrGetDistance(p.Minus(r).Plus (d), p.Plus (r).Plus (d)); - break; + DoLine(canvas, hcs, p.Plus (r).Plus (d), p.Plus (r).Minus(d)); + DoLine(canvas, hcs, p.Plus (r).Minus(d), p.Minus(r).Minus(d)); + DoLine(canvas, hcs, p.Minus(r).Minus(d), p.Minus(r).Plus (d)); + DoLine(canvas, hcs, p.Minus(r).Plus (d), p.Plus (r).Plus (d)); + return; } - case WHERE_DRAGGED: { - Vector p = SK.GetEntity(ptA)->PointGetNum(), - u = p.Plus(gu.WithMagnitude(8/SS.GW.scale)).Plus( - gr.WithMagnitude(8/SS.GW.scale)), - uu = u.Minus(gu.WithMagnitude(5/SS.GW.scale)), - ur = u.Minus(gr.WithMagnitude(5/SS.GW.scale)); + case Type::WHERE_DRAGGED: { + Vector p = SK.GetEntity(ptA)->PointGetNum(); + if(refs) refs->push_back(p); + Vector u = p.Plus(gu.WithMagnitude(8/camera.scale)).Plus( + gr.WithMagnitude(8/camera.scale)), + uu = u.Minus(gu.WithMagnitude(5/camera.scale)), + ur = u.Minus(gr.WithMagnitude(5/camera.scale)); // Draw four little crop marks, uniformly spaced (by ninety // degree rotations) around the point. int i; for(i = 0; i < 4; i++) { - LineDrawOrGetDistance(u, uu); - LineDrawOrGetDistance(u, ur); + DoLine(canvas, hcs, u, uu); + DoLine(canvas, hcs, u, ur); u = u.RotatedAbout(p, gn, PI/2); ur = ur.RotatedAbout(p, gn, PI/2); uu = uu.RotatedAbout(p, gn, PI/2); } - break; + return; } - case SAME_ORIENTATION: { + case Type::SAME_ORIENTATION: { for(int i = 0; i < 2; i++) { Entity *e = SK.GetEntity(i == 0 ? entityA : entityB); Quaternion q = e->NormalGetNum(); - Vector n = q.RotationN().WithMagnitude(25/SS.GW.scale); - Vector u = q.RotationU().WithMagnitude(6/SS.GW.scale); + Vector n = q.RotationN().WithMagnitude(25/camera.scale); + Vector u = q.RotationU().WithMagnitude(6/camera.scale); Vector p = SK.GetEntity(e->point[0])->PointGetNum(); - p = p.Plus(n.WithMagnitude(10/SS.GW.scale)); + p = p.Plus(n.WithMagnitude(10/camera.scale)); + if(refs) refs->push_back(p); - LineDrawOrGetDistance(p.Plus(u), p.Minus(u).Plus(n)); - LineDrawOrGetDistance(p.Minus(u), p.Plus(u).Plus(n)); + DoLine(canvas, hcs, p.Plus(u), p.Minus(u).Plus(n)); + DoLine(canvas, hcs, p.Minus(u), p.Plus(u).Plus(n)); } - break; + return; } - case EQUAL_ANGLE: { + case Type::EQUAL_ANGLE: { Vector ref; Entity *a = SK.GetEntity(entityA); Entity *b = SK.GetEntity(entityB); @@ -786,15 +796,17 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { da = da.ScaledBy(-1); } - DoArcForAngle(a0, da, b0, db, - da.WithMagnitude(40/SS.GW.scale), &ref, /*trim=*/false); - DoArcForAngle(c0, dc, d0, dd, - dc.WithMagnitude(40/SS.GW.scale), &ref, /*trim=*/false); + DoArcForAngle(canvas, hcs, a0, da, b0, db, + da.WithMagnitude(40/camera.scale), &ref, /*trim=*/false); + if(refs) refs->push_back(ref); + DoArcForAngle(canvas, hcs, c0, dc, d0, dd, + dc.WithMagnitude(40/camera.scale), &ref, /*trim=*/false); + if(refs) refs->push_back(ref); - break; + return; } - case ANGLE: { + case Type::ANGLE: { Entity *a = SK.GetEntity(entityA); Entity *b = SK.GetEntity(entityB); @@ -808,15 +820,16 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { } Vector ref; - DoArcForAngle(a0, da, b0, db, disp.offset, &ref, /*trim=*/true); - DoLabel(ref, labelPos, gr, gu); - break; + DoArcForAngle(canvas, hcs, a0, da, b0, db, disp.offset, &ref, /*trim=*/true); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); + if(refs) refs->push_back(ref); + return; } - case PERPENDICULAR: { + case Type::PERPENDICULAR: { Vector u = Vector::From(0, 0, 0), v = Vector::From(0, 0, 0); Vector rn, ru; - if(workplane.v == Entity::FREE_IN_3D.v) { + if(workplane == Entity::FREE_IN_3D) { rn = gn; ru = gu; } else { @@ -832,8 +845,8 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { // Calculate orientation of perpendicular sign only // once, so that it's the same both times it's drawn u = e->VectorGetNum(); - u = u.WithMagnitude(16/SS.GW.scale); - v = (rn.Cross(u)).WithMagnitude(16/SS.GW.scale); + u = u.WithMagnitude(16/camera.scale); + v = (rn.Cross(u)).WithMagnitude(16/camera.scale); // a bit of bias to stop it from flickering between the // two possibilities if(fabs(u.Dot(ru)) < fabs(v.Dot(ru)) + LENGTH_EPS) { @@ -844,32 +857,32 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { Vector p = e->VectorGetRefPoint(); Vector s = p.Plus(u).Plus(v); - LineDrawOrGetDistance(s, s.Plus(v)); - + DoLine(canvas, hcs, s, s.Plus(v)); Vector m = s.Plus(v.ScaledBy(0.5)); - LineDrawOrGetDistance(m, m.Plus(u)); + DoLine(canvas, hcs, m, m.Plus(u)); + if(refs) refs->push_back(m); } - break; + return; } - case CURVE_CURVE_TANGENT: - case CUBIC_LINE_TANGENT: - case ARC_LINE_TANGENT: { + case Type::CURVE_CURVE_TANGENT: + case Type::CUBIC_LINE_TANGENT: + case Type::ARC_LINE_TANGENT: { Vector textAt, u, v; - if(type == ARC_LINE_TANGENT) { + if(type == Type::ARC_LINE_TANGENT) { Entity *arc = SK.GetEntity(entityA); Entity *norm = SK.GetEntity(arc->normal); Vector c = SK.GetEntity(arc->point[0])->PointGetNum(); Vector p = SK.GetEntity(arc->point[other ? 2 : 1])->PointGetNum(); Vector r = p.Minus(c); - textAt = p.Plus(r.WithMagnitude(14/SS.GW.scale)); + textAt = p.Plus(r.WithMagnitude(14/camera.scale)); u = norm->NormalU(); v = norm->NormalV(); - } else if(type == CUBIC_LINE_TANGENT) { + } else if(type == Type::CUBIC_LINE_TANGENT) { Vector n; - if(workplane.v == Entity::FREE_IN_3D.v) { + if(workplane == Entity::FREE_IN_3D) { u = gr; v = gu; n = gn; @@ -885,7 +898,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { cubic->CubicGetStartNum(); Vector dir = SK.GetEntity(entityB)->VectorGetNum(); Vector out = n.Cross(dir); - textAt = p.Plus(out.WithMagnitude(14/SS.GW.scale)); + textAt = p.Plus(out.WithMagnitude(14/camera.scale)); } else { Vector n, dir; EntityBase *wn = SK.GetEntity(workplane)->Normal(); @@ -898,7 +911,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { // or an arc. if(other) { textAt = eA->EndpointFinish(); - if(eA->type == Entity::CUBIC) { + if(eA->type == Entity::Type::CUBIC) { dir = eA->CubicGetFinishTangentNum(); } else { dir = SK.GetEntity(eA->point[0])->PointGetNum().Minus( @@ -907,7 +920,7 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { } } else { textAt = eA->EndpointStart(); - if(eA->type == Entity::CUBIC) { + if(eA->type == Entity::Type::CUBIC) { dir = eA->CubicGetStartTangentNum(); } else { dir = SK.GetEntity(eA->point[0])->PointGetNum().Minus( @@ -916,102 +929,109 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { } } dir = n.Cross(dir); - textAt = textAt.Plus(dir.WithMagnitude(14/SS.GW.scale)); + textAt = textAt.Plus(dir.WithMagnitude(14/camera.scale)); } - if(dogd.drawing) { - ssglWriteTextRefCenter("T", Style::DefaultTextHeight(), - textAt, u, v, LineCallback, this); - } else { - dogd.refp = textAt; - Point2d ref = SS.GW.ProjectPoint(dogd.refp); - dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-10); - } - break; + Vector ex = VectorFont::Builtin()->GetExtents(textHeight, "T"); + canvas->DrawVectorText("T", textHeight, textAt.Minus(ex.ScaledBy(0.5)), + u.WithMagnitude(1), v.WithMagnitude(1), hcs); + if(refs) refs->push_back(textAt); + return; } - case PARALLEL: { + case Type::PARALLEL: { for(int i = 0; i < 2; i++) { Entity *e = SK.GetEntity(i == 0 ? entityA : entityB); Vector n = e->VectorGetNum(); - n = n.WithMagnitude(25/SS.GW.scale); - Vector u = (gn.Cross(n)).WithMagnitude(4/SS.GW.scale); + n = n.WithMagnitude(25/camera.scale); + Vector u = (gn.Cross(n)).WithMagnitude(4/camera.scale); Vector p = e->VectorGetRefPoint(); - LineDrawOrGetDistance(p.Plus(u), p.Plus(u).Plus(n)); - LineDrawOrGetDistance(p.Minus(u), p.Minus(u).Plus(n)); + DoLine(canvas, hcs, p.Plus(u), p.Plus(u).Plus(n)); + DoLine(canvas, hcs, p.Minus(u), p.Minus(u).Plus(n)); + if(refs) refs->push_back(p.Plus(n.ScaledBy(0.5))); } - break; + return; } - case EQUAL_RADIUS: { + case Type::EQUAL_RADIUS: { for(int i = 0; i < 2; i++) { - DoEqualRadiusTicks(i == 0 ? entityA : entityB); + Vector ref; + DoEqualRadiusTicks(canvas, hcs, i == 0 ? entityA : entityB, &ref); + if(refs) refs->push_back(ref); } - break; + return; } - case EQUAL_LINE_ARC_LEN: { + case Type::EQUAL_LINE_ARC_LEN: { Entity *line = SK.GetEntity(entityA); - DoEqualLenTicks( + Vector ref; + DoEqualLenTicks(canvas, hcs, SK.GetEntity(line->point[0])->PointGetNum(), SK.GetEntity(line->point[1])->PointGetNum(), - gn); - - DoEqualRadiusTicks(entityB); - break; + gn, &ref); + if(refs) refs->push_back(ref); + DoEqualRadiusTicks(canvas, hcs, entityB, &ref); + if(refs) refs->push_back(ref); + return; } - case LENGTH_RATIO: - case LENGTH_DIFFERENCE: - case EQUAL_LENGTH_LINES: { + case Type::LENGTH_RATIO: + case Type::LENGTH_DIFFERENCE: + case Type::EQUAL_LENGTH_LINES: { Vector a, b = Vector::From(0, 0, 0); for(int i = 0; i < 2; i++) { Entity *e = SK.GetEntity(i == 0 ? entityA : entityB); a = SK.GetEntity(e->point[0])->PointGetNum(); b = SK.GetEntity(e->point[1])->PointGetNum(); - if(workplane.v != Entity::FREE_IN_3D.v) { - DoProjectedPoint(&a); - DoProjectedPoint(&b); + if(workplane != Entity::FREE_IN_3D) { + DoProjectedPoint(canvas, hcs, &a); + DoProjectedPoint(canvas, hcs, &b); } - DoEqualLenTicks(a, b, gn); + Vector ref; + DoEqualLenTicks(canvas, hcs, a, b, gn, &ref); + if(refs) refs->push_back(ref); } - if((type == LENGTH_RATIO) || (type == LENGTH_DIFFERENCE)) { + if((type == Type::LENGTH_RATIO) || (type == Type::LENGTH_DIFFERENCE)) { Vector ref = ((a.Plus(b)).ScaledBy(0.5)).Plus(disp.offset); - DoLabel(ref, labelPos, gr, gu); + DoLabel(canvas, hcs, ref, labelPos, gr, gu); } - break; + return; } - case EQ_LEN_PT_LINE_D: { + case Type::EQ_LEN_PT_LINE_D: { Entity *forLen = SK.GetEntity(entityA); Vector a = SK.GetEntity(forLen->point[0])->PointGetNum(), b = SK.GetEntity(forLen->point[1])->PointGetNum(); - if(workplane.v != Entity::FREE_IN_3D.v) { - DoProjectedPoint(&a); - DoProjectedPoint(&b); + if(workplane != Entity::FREE_IN_3D) { + DoProjectedPoint(canvas, hcs, &a); + DoProjectedPoint(canvas, hcs, &b); } - DoEqualLenTicks(a, b, gn); + Vector refa; + DoEqualLenTicks(canvas, hcs, a, b, gn, &refa); + if(refs) refs->push_back(refa); Entity *ln = SK.GetEntity(entityB); Vector la = SK.GetEntity(ln->point[0])->PointGetNum(), lb = SK.GetEntity(ln->point[1])->PointGetNum(); Vector pt = SK.GetEntity(ptA)->PointGetNum(); - if(workplane.v != Entity::FREE_IN_3D.v) { - DoProjectedPoint(&pt); + if(workplane != Entity::FREE_IN_3D) { + DoProjectedPoint(canvas, hcs, &pt); la = la.ProjectInto(workplane); lb = lb.ProjectInto(workplane); } Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la)); - LineDrawOrGetDistance(pt, closest); - DoEqualLenTicks(pt, closest, gn); - break; + DoLine(canvas, hcs, pt, closest); + Vector refb; + DoEqualLenTicks(canvas, hcs, pt, closest, gn, &refb); + if(refs) refs->push_back(refb); + return; } - case EQ_PT_LN_DISTANCES: { + case Type::EQ_PT_LN_DISTANCES: { for(int i = 0; i < 2; i++) { Entity *ln = SK.GetEntity(i == 0 ? entityA : entityB); Vector la = SK.GetEntity(ln->point[0])->PointGetNum(), @@ -1019,29 +1039,31 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) { Entity *pte = SK.GetEntity(i == 0 ? ptA : ptB); Vector pt = pte->PointGetNum(); - if(workplane.v != Entity::FREE_IN_3D.v) { - DoProjectedPoint(&pt); + if(workplane != Entity::FREE_IN_3D) { + DoProjectedPoint(canvas, hcs, &pt); la = la.ProjectInto(workplane); lb = lb.ProjectInto(workplane); } Vector closest = pt.ClosestPointOnLine(la, lb.Minus(la)); + DoLine(canvas, hcs, pt, closest); - LineDrawOrGetDistance(pt, closest); - DoEqualLenTicks(pt, closest, gn); + Vector ref; + DoEqualLenTicks(canvas, hcs, pt, closest, gn, &ref); + if(refs) refs->push_back(ref); } - break; + return; } { - case SYMMETRIC: + case Type::SYMMETRIC: Vector n; n = SK.GetEntity(entityA)->Normal()->NormalN(); goto s; - case SYMMETRIC_HORIZ: + case Type::SYMMETRIC_HORIZ: n = SK.GetEntity(workplane)->Normal()->NormalU(); goto s; - case SYMMETRIC_VERT: + case Type::SYMMETRIC_VERT: n = SK.GetEntity(workplane)->Normal()->NormalV(); goto s; - case SYMMETRIC_LINE: { + case Type::SYMMETRIC_LINE: { Entity *ln = SK.GetEntity(entityA); Vector la = SK.GetEntity(ln->point[0])->PointGetNum(), lb = SK.GetEntity(ln->point[1])->PointGetNum(); @@ -1065,23 +1087,24 @@ s: // they might not be in the same direction, even when the // constraint is fully solved. d = n.ScaledBy(d.Dot(n)); - d = d.WithMagnitude(20/SS.GW.scale); + d = d.WithMagnitude(20/camera.scale); Vector tip = tail.Plus(d); - LineDrawOrGetDistance(tail, tip); - d = d.WithMagnitude(9/SS.GW.scale); - LineDrawOrGetDistance(tip, tip.Minus(d.RotatedAbout(gn, 0.6))); - LineDrawOrGetDistance(tip, tip.Minus(d.RotatedAbout(gn, -0.6))); + DoLine(canvas, hcs, tail, tip); + d = d.WithMagnitude(9/camera.scale); + DoLine(canvas, hcs, tip, tip.Minus(d.RotatedAbout(gn, 0.6))); + DoLine(canvas, hcs, tip, tip.Minus(d.RotatedAbout(gn, -0.6))); + if(refs) refs->push_back(tip); } - break; + return; } - case AT_MIDPOINT: - case HORIZONTAL: - case VERTICAL: + case Type::AT_MIDPOINT: + case Type::HORIZONTAL: + case Type::VERTICAL: if(entityA.v) { Vector r, u, n; - if(workplane.v == Entity::FREE_IN_3D.v) { + if(workplane == Entity::FREE_IN_3D) { r = gr; u = gu; n = gn; } else { r = SK.GetEntity(workplane)->Normal()->NormalU(); @@ -1094,23 +1117,26 @@ s: Vector b = SK.GetEntity(e->point[1])->PointGetNum(); Vector m = (a.ScaledBy(0.5)).Plus(b.ScaledBy(0.5)); Vector offset = (a.Minus(b)).Cross(n); - offset = offset.WithMagnitude(13/SS.GW.scale); + offset = offset.WithMagnitude(textHeight); // Draw midpoint constraint on other side of line, so that // a line can be midpoint and horizontal at same time. - if(type == AT_MIDPOINT) offset = offset.ScaledBy(-1); - - if(dogd.drawing) { - const char *s = (type == HORIZONTAL) ? "H" : ( - (type == VERTICAL) ? "V" : ( - (type == AT_MIDPOINT) ? "M" : NULL)); - - ssglWriteTextRefCenter(s, Style::DefaultTextHeight(), - m.Plus(offset), r, u, LineCallback, this); - } else { - dogd.refp = m.Plus(offset); - Point2d ref = SS.GW.ProjectPoint(dogd.refp); - dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-10); + if(type == Type::AT_MIDPOINT) offset = offset.ScaledBy(-1); + + std::string s; + switch(type) { + case Type::HORIZONTAL: s = "H"; break; + case Type::VERTICAL: s = "V"; break; + case Type::AT_MIDPOINT: s = "M"; break; + default: ssassert(false, "Unexpected constraint type"); } + Vector o = m.Plus(offset).Plus(u.WithMagnitude(textHeight/5)), + ex = VectorFont::Builtin()->GetExtents(textHeight, s); + Vector shift = r.WithMagnitude(ex.x).Plus( + u.WithMagnitude(ex.y)); + + canvas->DrawVectorText(s, textHeight, o.Minus(shift.ScaledBy(0.5)), + r.WithMagnitude(1), u.WithMagnitude(1), hcs); + if(refs) refs->push_back(o); } else { Vector a = SK.GetEntity(ptA)->PointGetNum(); Vector b = SK.GetEntity(ptB)->PointGetNum(); @@ -1124,37 +1150,28 @@ s: for(i = 0; i < 2; i++) { Vector o = (i == 0) ? a : b; Vector oo = (i == 0) ? a.Minus(b) : b.Minus(a); - Vector d = (type == HORIZONTAL) ? cu : cv; + Vector d = (type == Type::HORIZONTAL) ? cu : cv; if(oo.Dot(d) < 0) d = d.ScaledBy(-1); Vector dp = cn.Cross(d); - d = d.WithMagnitude(14/SS.GW.scale); + d = d.WithMagnitude(14/camera.scale); Vector c = o.Minus(d); - LineDrawOrGetDistance(o, c); - d = d.WithMagnitude(3/SS.GW.scale); - dp = dp.WithMagnitude(2/SS.GW.scale); - if(dogd.drawing) { - glBegin(GL_QUADS); - ssglVertex3v((c.Plus(d)).Plus(dp)); - ssglVertex3v((c.Minus(d)).Plus(dp)); - ssglVertex3v((c.Minus(d)).Minus(dp)); - ssglVertex3v((c.Plus(d)).Minus(dp)); - glEnd(); - } else { - Point2d ref = SS.GW.ProjectPoint(c); - dogd.dmin = min(dogd.dmin, ref.DistanceTo(dogd.mp)-6); - } + DoLine(canvas, hcs, o, c); + d = d.WithMagnitude(3/camera.scale); + dp = dp.WithMagnitude(2/camera.scale); + canvas->DrawQuad((c.Plus(d)).Plus(dp), + (c.Minus(d)).Plus(dp), + (c.Minus(d)).Minus(dp), + (c.Plus(d)).Minus(dp), + hcf); + if(refs) refs->push_back(c); } } - break; + return; - case COMMENT: { - if(dogd.drawing && disp.style.v) { - ssglLineWidth(Style::Width(disp.style)); - ssglColorRGB(Style::Color(disp.style)); - } + case Type::COMMENT: { Vector u, v; - if(workplane.v == Entity::FREE_IN_3D.v) { + if(workplane == Entity::FREE_IN_3D) { u = gr; v = gu; } else { @@ -1162,66 +1179,48 @@ s: u = norm->NormalU(); v = norm->NormalV(); } - DoLabel(disp.offset, labelPos, u, v); - break; - } - default: oops(); + if(disp.style.v != 0) { + RgbaColor color = stroke.color; + stroke = Style::Stroke(disp.style); + stroke.layer = Canvas::Layer::FRONT; + if(how != DrawAs::DEFAULT) { + stroke.color = color; + } + hcs = canvas->GetStroke(stroke); + } + DoLabel(canvas, hcs, disp.offset, labelPos, u, v); + if(refs) refs->push_back(disp.offset); + return; + } } + ssassert(false, "Unexpected constraint type"); } -void Constraint::Draw(void) { - dogd.drawing = true; - dogd.sel = NULL; - hStyle hs = GetStyle(); - - ssglLineWidth(Style::Width(hs)); - ssglColorRGB(Style::Color(hs)); - - DrawOrGetDistance(NULL); +void Constraint::Draw(DrawAs how, Canvas *canvas) { + DoLayout(how, canvas, NULL, NULL); } -double Constraint::GetDistance(Point2d mp) { - dogd.drawing = false; - dogd.sel = NULL; - dogd.mp = mp; - dogd.dmin = 1e12; - - DrawOrGetDistance(NULL); - - return dogd.dmin; -} - -Vector Constraint::GetLabelPos(void) { - dogd.drawing = false; - dogd.sel = NULL; - dogd.mp.x = 0; dogd.mp.y = 0; - dogd.dmin = 1e12; - +Vector Constraint::GetLabelPos(const Camera &camera) { Vector p; - DrawOrGetDistance(&p); - return p; -} -Vector Constraint::GetReferencePos(void) { - dogd.drawing = false; - dogd.sel = NULL; + ObjectPicker canvas = {}; + canvas.camera = camera; + DoLayout(DrawAs::DEFAULT, &canvas, &p, NULL); + canvas.Clear(); - dogd.refp = SS.GW.offset.ScaledBy(-1); - DrawOrGetDistance(NULL); - - return dogd.refp; + return p; } -void Constraint::GetEdges(SEdgeList *sel) { - dogd.drawing = true; - dogd.sel = sel; - DrawOrGetDistance(NULL); - dogd.sel = NULL; +void Constraint::GetReferencePoints(const Camera &camera, std::vector *refs) { + ObjectPicker canvas = {}; + canvas.camera = camera; + DoLayout(DrawAs::DEFAULT, &canvas, NULL, refs); + canvas.Clear(); } -bool Constraint::IsStylable() { - if(type == COMMENT) return true; +bool Constraint::IsStylable() const { + if(type == Type::COMMENT) return true; return false; } @@ -1230,18 +1229,18 @@ hStyle Constraint::GetStyle() const { return { Style::CONSTRAINT }; } -bool Constraint::HasLabel() { +bool Constraint::HasLabel() const { switch(type) { - case COMMENT: - case PT_PT_DISTANCE: - case PT_PLANE_DISTANCE: - case PT_LINE_DISTANCE: - case PT_FACE_DISTANCE: - case PROJ_PT_DISTANCE: - case LENGTH_RATIO: - case LENGTH_DIFFERENCE: - case DIAMETER: - case ANGLE: + case Type::COMMENT: + case Type::PT_PT_DISTANCE: + case Type::PT_PLANE_DISTANCE: + case Type::PT_LINE_DISTANCE: + case Type::PT_FACE_DISTANCE: + case Type::PROJ_PT_DISTANCE: + case Type::LENGTH_RATIO: + case Type::LENGTH_DIFFERENCE: + case Type::DIAMETER: + case Type::ANGLE: return true; default: diff --git a/src/drawentity.cpp b/src/drawentity.cpp index 39bc085..fab4071 100644 --- a/src/drawentity.cpp +++ b/src/drawentity.cpp @@ -7,7 +7,7 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -std::string Entity::DescriptionString(void) { +std::string Entity::DescriptionString() const { if(h.isFromRequest()) { Request *r = SK.GetRequest(h.request()); return r->DescriptionString(); @@ -17,143 +17,35 @@ std::string Entity::DescriptionString(void) { } } -void Entity::LineDrawOrGetDistance(Vector a, Vector b, bool maybeFat, int data) { - if(dogd.drawing) { - // Draw lines from active group in front of those from previous - ssglDepthRangeOffset((group.v == SS.GW.activeGroup.v) ? 4 : 3); - // Narrow lines are drawn as lines, but fat lines must be drawn as - // filled polygons, to get the line join style right. - ssglStippledLine(a, b, dogd.lineWidth, dogd.stippleType, dogd.stippleScale, maybeFat); - ssglDepthRangeOffset(0); - } else { - Point2d ap = SS.GW.ProjectPoint(a); - Point2d bp = SS.GW.ProjectPoint(b); - - double d = dogd.mp.DistanceToLine(ap, bp.Minus(ap), true); - // A little bit easier to select in the active group - if(group.v == SS.GW.activeGroup.v) d -= 1; - if(d < dogd.dmin) { - dogd.dmin = d; - dogd.data = data; - } - } - dogd.refp = (a.Plus(b)).ScaledBy(0.5); -} - -void Entity::DrawAll(bool drawAsHidden) { - // This handles points as a special case, because I seem to be able - // to get a huge speedup that way, by consolidating stuff to gl. - int i; - if(SS.GW.showPoints) { - double s = 3.5/SS.GW.scale; - Vector r = SS.GW.projRight.ScaledBy(s); - Vector d = SS.GW.projUp.ScaledBy(s); - ssglColorRGB(Style::Color(Style::DATUM)); - ssglDepthRangeOffset(6); - glBegin(GL_QUADS); - for(i = 0; i < SK.entity.n; i++) { - Entity *e = &(SK.entity.elem[i]); - if(!e->IsPoint()) continue; - if(!(SK.GetGroup(e->group)->IsVisible())) continue; - if(e->forceHidden) continue; - - Vector v = e->PointGetNum(); - - // If we're analyzing the sketch to show the degrees of freedom, - // then we draw big colored squares over the points that are - // free to move. - bool free = false; - if(e->type == POINT_IN_3D) { - Param *px = SK.GetParam(e->param[0]), - *py = SK.GetParam(e->param[1]), - *pz = SK.GetParam(e->param[2]); - - free = (px->free) || (py->free) || (pz->free); - } else if(e->type == POINT_IN_2D) { - Param *pu = SK.GetParam(e->param[0]), - *pv = SK.GetParam(e->param[1]); - - free = (pu->free) || (pv->free); - } - if(free) { - Vector re = r.ScaledBy(2.5), de = d.ScaledBy(2.5); - - ssglColorRGB(Style::Color(Style::ANALYZE)); - ssglVertex3v(v.Plus (re).Plus (de)); - ssglVertex3v(v.Plus (re).Minus(de)); - ssglVertex3v(v.Minus(re).Minus(de)); - ssglVertex3v(v.Minus(re).Plus (de)); - ssglColorRGB(Style::Color(Style::DATUM)); - } - - ssglVertex3v(v.Plus (r).Plus (d)); - ssglVertex3v(v.Plus (r).Minus(d)); - ssglVertex3v(v.Minus(r).Minus(d)); - ssglVertex3v(v.Minus(r).Plus (d)); - } - glEnd(); - ssglDepthRangeOffset(0); - } - - for(i = 0; i < SK.entity.n; i++) { - Entity *e = &(SK.entity.elem[i]); - if(e->IsPoint()) { - continue; // already handled - } - e->Draw(drawAsHidden); - } -} - -void Entity::Draw(bool drawAsHidden) { - hStyle hs = Style::ForEntity(h); - dogd.lineWidth = Style::Width(hs); - if(drawAsHidden) { - dogd.stippleType = Style::PatternType({ Style::HIDDEN_EDGE }); - dogd.stippleScale = Style::StippleScaleMm({ Style::HIDDEN_EDGE }); - } else { - dogd.stippleType = Style::PatternType(hs); - dogd.stippleScale = Style::StippleScaleMm(hs); - } - ssglLineWidth((float)dogd.lineWidth); - ssglColorRGB(Style::Color(hs)); - - dogd.drawing = true; - DrawOrGetDistance(); -} - -void Entity::GenerateEdges(SEdgeList *el, bool includingConstruction) { - if(construction && !includingConstruction) return; - +void Entity::GenerateEdges(SEdgeList *el) { SBezierList *sbl = GetOrGenerateBezierCurves(); - int i, j; - for(i = 0; i < sbl->l.n; i++) { - SBezier *sb = &(sbl->l.elem[i]); + for(int i = 0; i < sbl->l.n; i++) { + SBezier *sb = &(sbl->l[i]); List lv = {}; sb->MakePwlInto(&lv); - for(j = 1; j < lv.n; j++) { - el->AddEdge(lv.elem[j-1], lv.elem[j], style.v, i); + for(int j = 1; j < lv.n; j++) { + el->AddEdge(lv[j-1], lv[j], style.v, i); } lv.Clear(); } - } SBezierList *Entity::GetOrGenerateBezierCurves() { - if(beziers.l.n == 0) + if(beziers.l.IsEmpty()) GenerateBezierCurves(&beziers); return &beziers; } SEdgeList *Entity::GetOrGenerateEdges() { - if(edges.l.n != 0) { + if(!edges.l.IsEmpty()) { if(EXACT(edgesChordTol == SS.ChordTolMm())) return &edges; edges.l.Clear(); } - if(edges.l.n == 0) - GenerateEdges(&edges, /*includingConstruction=*/true); + if(edges.l.IsEmpty()) + GenerateEdges(&edges); edgesChordTol = SS.ChordTolMm(); return &edges; } @@ -161,8 +53,8 @@ SEdgeList *Entity::GetOrGenerateEdges() { BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) { SBezierList *sbl = GetOrGenerateBezierCurves(); - // We don't bother with bounding boxes for normals, workplanes, etc. - *hasBBox = (IsPoint() || sbl->l.n > 0); + // We don't bother with bounding boxes for workplanes, etc. + *hasBBox = (IsPoint() || IsNormal() || !sbl->l.IsEmpty()); if(!*hasBBox) return {}; if(screenBBoxValid) @@ -171,69 +63,106 @@ BBox Entity::GetOrGenerateScreenBBox(bool *hasBBox) { if(IsPoint()) { Vector proj = SS.GW.ProjectPoint3(PointGetNum()); screenBBox = BBox::From(proj, proj); - } else if(sbl->l.n > 0) { - Vector first = SS.GW.ProjectPoint3(sbl->l.elem[0].ctrl[0]); + } else if(IsNormal()) { + Vector proj = SK.GetEntity(point[0])->PointGetNum(); + screenBBox = BBox::From(proj, proj); + } else if(!sbl->l.IsEmpty()) { + Vector first = SS.GW.ProjectPoint3(sbl->l[0].ctrl[0]); screenBBox = BBox::From(first, first); - for(int i = 0; i < sbl->l.n; i++) { - SBezier *sb = &sbl->l.elem[i]; - for(int i = 0; i <= sb->deg; i++) { - screenBBox.Include(SS.GW.ProjectPoint3(sb->ctrl[i])); - } + for(auto &sb : sbl->l) { + for(int i = 0; i < sb.deg; ++i) { screenBBox.Include(SS.GW.ProjectPoint3(sb.ctrl[i])); } } - } else oops(); - - // Enlarge the bounding box to consider selection radius. - screenBBox.minp.x -= SELECTION_RADIUS; - screenBBox.minp.y -= SELECTION_RADIUS; - screenBBox.maxp.x += SELECTION_RADIUS; - screenBBox.maxp.y += SELECTION_RADIUS; + } else + ssassert(false, "Expected entity to be a point or have beziers"); screenBBoxValid = true; return screenBBox; } -double Entity::GetDistance(Point2d mp) { - dogd.drawing = false; - dogd.mp = mp; - dogd.dmin = 1e12; +void Entity::GetReferencePoints(std::vector *refs) { + switch(type) { + case Type::POINT_N_COPY: + case Type::POINT_N_TRANS: + case Type::POINT_N_ROT_TRANS: + case Type::POINT_N_ROT_AA: + case Type::POINT_N_ROT_AXIS_TRANS: + case Type::POINT_IN_3D: + case Type::POINT_IN_2D: + refs->push_back(PointGetNum()); + break; + + case Type::NORMAL_N_COPY: + case Type::NORMAL_N_ROT: + case Type::NORMAL_N_ROT_AA: + case Type::NORMAL_IN_3D: + case Type::NORMAL_IN_2D: + case Type::WORKPLANE: + case Type::CIRCLE: + case Type::ARC_OF_CIRCLE: + case Type::CUBIC: + case Type::CUBIC_PERIODIC: + case Type::TTF_TEXT: + case Type::IMAGE: + refs->push_back(SK.GetEntity(point[0])->PointGetNum()); + break; - DrawOrGetDistance(); + case Type::LINE_SEGMENT: { + Vector a = SK.GetEntity(point[0])->PointGetNum(), + b = SK.GetEntity(point[1])->PointGetNum(); + refs->push_back(b.Plus(a.Minus(b).ScaledBy(0.5))); + break; + } - return dogd.dmin; + case Type::DISTANCE: + case Type::DISTANCE_N_COPY: + case Type::FACE_NORMAL_PT: + case Type::FACE_XPROD: + case Type::FACE_N_ROT_TRANS: + case Type::FACE_N_TRANS: + case Type::FACE_N_ROT_AA: + case Type::FACE_ROT_NORMAL_PT: + case Type::FACE_N_ROT_AXIS_TRANS: + break; + } } -Vector Entity::GetReferencePos(void) { - dogd.drawing = false; +int Entity::GetPositionOfPoint(const Camera &camera, Point2d p) { + int position; - dogd.refp = SS.GW.offset.ScaledBy(-1); - DrawOrGetDistance(); + ObjectPicker canvas = {}; + canvas.camera = camera; + canvas.point = p; + canvas.minDistance = 1e12; + Draw(DrawAs::DEFAULT, &canvas); + position = canvas.position; + canvas.Clear(); - return dogd.refp; + return position; } -bool Entity::IsStylable() { +bool Entity::IsStylable() const { if(IsPoint()) return false; if(IsWorkplane()) return false; if(IsNormal()) return false; return true; } -bool Entity::IsVisible(void) { +bool Entity::IsVisible() const { Group *g = SK.GetGroup(group); - if(g->h.v == Group::HGROUP_REFERENCES.v && IsNormal()) { + if(g->h == Group::HGROUP_REFERENCES && IsNormal()) { // The reference normals are always shown return true; } if(!(g->IsVisible())) return false; - // Don't check if points are hidden; this gets called only for - // selected or hovered points, and those should always be shown. + if(IsPoint() && !SS.GW.showPoints) return false; if(IsNormal() && !SS.GW.showNormals) return false; + if(construction && !SS.GW.showConstruction) return false; if(!SS.GW.showWorkplanes) { if(IsWorkplane() && !h.isFromRequest()) { - if(g->h.v != SS.GW.activeGroup.v) { + if(g->h != SS.GW.activeGroup) { // The group-associated workplanes are hidden outside // their group. return false; @@ -251,10 +180,28 @@ bool Entity::IsVisible(void) { return true; } +// entities that were created via some copy types will not be +// draggable with the mouse. We identify the undraggables here +bool Entity::CanBeDragged() const { + // a numeric copy can not move + if(type == Entity::Type::POINT_N_COPY) return false; + // these transforms applied zero times can not be moved + if(((type == Entity::Type::POINT_N_TRANS) || + (type == Entity::Type::POINT_N_ROT_AA) || + (type == Entity::Type::POINT_N_ROT_AXIS_TRANS)) + && (timesApplied == 0)) return false; + // for these types of entities the first point will indicate draggability + if(HasEndpoints() || type == Entity::Type::CIRCLE) { + return SK.GetEntity(point[0])->CanBeDragged(); + } + // if we're not certain it can't be dragged then default to true + return true; +} + void Entity::CalculateNumerical(bool forExport) { if(IsPoint()) actPoint = PointGetNum(); if(IsNormal()) actNormal = NormalGetNum(); - if(type == DISTANCE || type == DISTANCE_N_COPY) { + if(type == Type::DISTANCE || type == Type::DISTANCE_N_COPY) { actDistance = DistanceGetNum(); } if(IsFace()) { @@ -271,22 +218,18 @@ void Entity::CalculateNumerical(bool forExport) { } } -bool Entity::PointIsFromReferences(void) { - return h.request().IsFromReferences(); -} - //----------------------------------------------------------------------------- // Compute a cubic, second derivative continuous, interpolating spline. Same // routine for periodic splines (in a loop) or open splines (with specified // end tangents). //----------------------------------------------------------------------------- -void Entity::ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) { +void Entity::ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) const { static const int MAX_N = BandedMatrix::MAX_UNKNOWNS; int ep = extraPoints; // The number of unknowns to solve for. int n = periodic ? 3 + ep : ep; - if(n >= MAX_N) oops(); + ssassert(n < MAX_N, "Too many unknowns"); // The number of on-curve points, one more than the number of segments. int pts = periodic ? 4 + ep : 2 + ep; @@ -372,9 +315,13 @@ void Entity::ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) { } else { // The wrapping would work, except when n = 1 and everything // wraps to zero... - if(i > 0) bm.A[i][i - 1] = eq.x; - bm.A[i][i] = eq.y; - if(i < (n-1)) bm.A[i][i + 1] = eq.z; + if(i > 0) { + bm.A[i][i - 1] = eq.x; + } + bm.A[i][i] = eq.y; + if(i < (n-1)) { + bm.A[i][i + 1] = eq.z; + } } } bm.Solve(); @@ -413,13 +360,13 @@ void Entity::ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) { } } -void Entity::GenerateBezierCurves(SBezierList *sbl) { +void Entity::GenerateBezierCurves(SBezierList *sbl) const { SBezier sb; int i = sbl->l.n; switch(type) { - case LINE_SEGMENT: { + case Type::LINE_SEGMENT: { Vector a = SK.GetEntity(point[0])->PointGetNum(); Vector b = SK.GetEntity(point[1])->PointGetNum(); sb = SBezier::From(a, b); @@ -427,16 +374,16 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) { sbl->l.Add(&sb); break; } - case CUBIC: - ComputeInterpolatingSpline(sbl, false); + case Type::CUBIC: + ComputeInterpolatingSpline(sbl, /*periodic=*/false); break; - case CUBIC_PERIODIC: - ComputeInterpolatingSpline(sbl, true); + case Type::CUBIC_PERIODIC: + ComputeInterpolatingSpline(sbl, /*periodic=*/true); break; - case CIRCLE: - case ARC_OF_CIRCLE: { + case Type::CIRCLE: + case Type::ARC_OF_CIRCLE: { Vector center = SK.GetEntity(point[0])->PointGetNum(); Quaternion q = SK.GetEntity(normal)->NormalGetNum(); Vector u = q.RotationU(), v = q.RotationV(); @@ -449,7 +396,7 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) { break; } - if(type == CIRCLE) { + if(type == Type::CIRCLE) { thetaa = 0; thetab = 2*PI; dtheta = 2*PI; @@ -497,7 +444,7 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) { break; } - case TTF_TEXT: { + case Type::TTF_TEXT: { Vector topLeft = SK.GetEntity(point[0])->PointGetNum(); Vector botLeft = SK.GetEntity(point[1])->PointGetNum(); Vector n = Normal()->NormalN(); @@ -515,135 +462,208 @@ void Entity::GenerateBezierCurves(SBezierList *sbl) { // Record our style for all of the Beziers that we just created. for(; i < sbl->l.n; i++) { - sbl->l.elem[i].auxA = style.v; + sbl->l[i].auxA = style.v; } } -void Entity::DrawOrGetDistance(void) { - // If we're about to perform hit testing on an entity, consider - // whether the pointer is inside its bounding box first. - if(!dogd.drawing) { - bool hasBBox; - BBox box = GetOrGenerateScreenBBox(&hasBBox); - if(hasBBox && !box.Contains(dogd.mp)) - return; +void Entity::Draw(DrawAs how, Canvas *canvas) { + if(!(how == DrawAs::HOVERED || how == DrawAs::SELECTED) && + !IsVisible()) return; + + int zIndex; + if(IsPoint()) { + zIndex = 5; + } else if(how == DrawAs::HIDDEN) { + zIndex = 2; + } else if(group != SS.GW.activeGroup) { + zIndex = 3; + } else { + zIndex = 4; } - if(!IsVisible()) return; + hStyle hs; + if(IsPoint()) { + hs.v = Style::DATUM; + } else if(IsNormal() || type == Type::WORKPLANE) { + hs.v = Style::NORMALS; + } else { + hs = Style::ForEntity(h); + } + + Canvas::Stroke stroke = Style::Stroke(hs); + switch(how) { + case DrawAs::DEFAULT: + stroke.layer = Canvas::Layer::NORMAL; + break; + + case DrawAs::OVERLAY: + stroke.layer = Canvas::Layer::FRONT; + break; + + case DrawAs::HIDDEN: + stroke.layer = Canvas::Layer::OCCLUDED; + stroke.stipplePattern = Style::PatternType({ Style::HIDDEN_EDGE }); + stroke.stippleScale = Style::Get({ Style::HIDDEN_EDGE })->stippleScale; + break; + + case DrawAs::HOVERED: + stroke.layer = Canvas::Layer::FRONT; + stroke.color = Style::Color(Style::HOVERED); + break; + + case DrawAs::SELECTED: + stroke.layer = Canvas::Layer::FRONT; + stroke.color = Style::Color(Style::SELECTED); + break; + } + stroke.zIndex = zIndex; + Canvas::hStroke hcs = canvas->GetStroke(stroke); switch(type) { - case POINT_N_COPY: - case POINT_N_TRANS: - case POINT_N_ROT_TRANS: - case POINT_N_ROT_AA: - case POINT_IN_3D: - case POINT_IN_2D: { - Vector v = PointGetNum(); - dogd.refp = v; - - if(dogd.drawing) { - double s = 3.5; - Vector r = SS.GW.projRight.ScaledBy(s/SS.GW.scale); - Vector d = SS.GW.projUp.ScaledBy(s/SS.GW.scale); - - ssglColorRGB(Style::Color(Style::DATUM)); - ssglDepthRangeOffset(6); - glBegin(GL_QUADS); - ssglVertex3v(v.Plus (r).Plus (d)); - ssglVertex3v(v.Plus (r).Minus(d)); - ssglVertex3v(v.Minus(r).Minus(d)); - ssglVertex3v(v.Minus(r).Plus (d)); - glEnd(); - ssglDepthRangeOffset(0); - } else { - Point2d pp = SS.GW.ProjectPoint(v); - dogd.dmin = pp.DistanceTo(dogd.mp) - 6; + case Type::POINT_N_COPY: + case Type::POINT_N_TRANS: + case Type::POINT_N_ROT_TRANS: + case Type::POINT_N_ROT_AA: + case Type::POINT_N_ROT_AXIS_TRANS: + case Type::POINT_IN_3D: + case Type::POINT_IN_2D: { + if(how == DrawAs::HIDDEN) return; + + // If we're analyzing the sketch to show the degrees of freedom, + // then we draw big colored squares over the points that are + // free to move. + bool free = false; + if(type == Type::POINT_IN_3D) { + Param *px = SK.GetParam(param[0]), + *py = SK.GetParam(param[1]), + *pz = SK.GetParam(param[2]); + + free = px->free || py->free || pz->free; + } else if(type == Type::POINT_IN_2D) { + Param *pu = SK.GetParam(param[0]), + *pv = SK.GetParam(param[1]); + + free = pu->free || pv->free; } - break; + + Canvas::Stroke pointStroke = {}; + pointStroke.layer = (free) ? Canvas::Layer::FRONT : stroke.layer; + pointStroke.zIndex = stroke.zIndex; + pointStroke.color = stroke.color; + pointStroke.width = 7.0; + pointStroke.unit = Canvas::Unit::PX; + Canvas::hStroke hcsPoint = canvas->GetStroke(pointStroke); + + if(free) { + Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE); + analyzeStroke.width = 14.0; + analyzeStroke.layer = Canvas::Layer::FRONT; + Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke); + + canvas->DrawPoint(PointGetNum(), hcsAnalyze); + } + + canvas->DrawPoint(PointGetNum(), hcsPoint); + return; } - case NORMAL_N_COPY: - case NORMAL_N_ROT: - case NORMAL_N_ROT_AA: - case NORMAL_IN_3D: - case NORMAL_IN_2D: { - int i; - for(i = 0; i < 2; i++) { - if(i == 0 && !SS.GW.showNormals) { - // When the normals are hidden, we will continue to show - // the coordinate axes at the bottom left corner, but - // not at the origin. - continue; - } + case Type::NORMAL_N_COPY: + case Type::NORMAL_N_ROT: + case Type::NORMAL_N_ROT_AA: + case Type::NORMAL_IN_3D: + case Type::NORMAL_IN_2D: { + const Camera &camera = canvas->GetCamera(); + + if(how == DrawAs::HIDDEN) return; - hRequest hr = h.request(); - // Always draw the x, y, and z axes in red, green, and blue; - // brighter for the ones at the bottom left of the screen, - // dimmer for the ones at the model origin. - int f = (i == 0 ? 100 : 255); - if(hr.v == Request::HREQUEST_REFERENCE_XY.v) { - if(dogd.drawing) - ssglColorRGB(RGBi(0, 0, f)); - } else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) { - if(dogd.drawing) - ssglColorRGB(RGBi(f, 0, 0)); - } else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) { - if(dogd.drawing) - ssglColorRGB(RGBi(0, f, 0)); + for(int i = 0; i < 2; i++) { + bool asReference = (i == 1); + if(asReference) { + if(!h.request().IsFromReferences()) continue; } else { - if(dogd.drawing) - ssglColorRGB(Style::Color(Style::NORMALS)); - if(i > 0) break; + if(!SK.GetGroup(group)->IsVisible() || !SS.GW.showNormals) continue; + } + + stroke.layer = (asReference) ? Canvas::Layer::FRONT : Canvas::Layer::NORMAL; + if(how != DrawAs::HOVERED && how != DrawAs::SELECTED) { + // Always draw the x, y, and z axes in red, green, and blue; + // brighter for the ones at the bottom left of the screen, + // dimmer for the ones at the model origin. + hRequest hr = h.request(); + uint8_t luma = (asReference) ? 255 : 100; + if(hr == Request::HREQUEST_REFERENCE_XY) { + stroke.color = RgbaColor::From(0, 0, luma); + } else if(hr == Request::HREQUEST_REFERENCE_YZ) { + stroke.color = RgbaColor::From(luma, 0, 0); + } else if(hr == Request::HREQUEST_REFERENCE_ZX) { + stroke.color = RgbaColor::From(0, luma, 0); + } } Quaternion q = NormalGetNum(); Vector tail; - if(i == 0) { - tail = SK.GetEntity(point[0])->PointGetNum(); - if(dogd.drawing) - ssglLineWidth(1); - } else { + if(asReference) { // Draw an extra copy of the x, y, and z axes, that's // always in the corner of the view and at the front. // So those are always available, perhaps useful. - double s = SS.GW.scale; - double h = 60 - SS.GW.height/2; - double w = 60 - SS.GW.width/2; - tail = SS.GW.projRight.ScaledBy(w/s).Plus( - SS.GW.projUp. ScaledBy(h/s)).Minus(SS.GW.offset); - if(dogd.drawing) { - ssglDepthRangeLockToFront(true); - ssglLineWidth(2); + stroke.width = 2; + double s = camera.scale; + double h = 60 - camera.height / 2.0; + double w = 60 - camera.width / 2.0; + // Shift the axis to the right if they would overlap with the toolbar. + if(SS.showToolbar) { + if(h + 30 > -(34*16 + 3*16 + 8) / 2) + w += 60; } + tail = camera.projRight.ScaledBy(w/s).Plus( + camera.projUp. ScaledBy(h/s)).Minus(camera.offset); + } else { + tail = SK.GetEntity(point[0])->PointGetNum(); } + tail = camera.AlignToPixelGrid(tail); - Vector v = (q.RotationN()).WithMagnitude(50/SS.GW.scale); + hcs = canvas->GetStroke(stroke); + Vector v = (q.RotationN()).WithMagnitude(50.0 / camera.scale); Vector tip = tail.Plus(v); - LineDrawOrGetDistance(tail, tip); + canvas->DrawLine(tail, tip, hcs); - v = v.WithMagnitude(12/SS.GW.scale); + v = v.WithMagnitude(12.0 / camera.scale); Vector axis = q.RotationV(); - LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis, 0.6))); - LineDrawOrGetDistance(tip,tip.Minus(v.RotatedAbout(axis,-0.6))); + canvas->DrawLine(tip, tip.Minus(v.RotatedAbout(axis, 0.6)), hcs); + canvas->DrawLine(tip, tip.Minus(v.RotatedAbout(axis, -0.6)), hcs); + + if(type == Type::NORMAL_IN_3D) { + Param *nw = SK.GetParam(param[0]), + *nx = SK.GetParam(param[1]), + *ny = SK.GetParam(param[2]), + *nz = SK.GetParam(param[3]); + + if(nw->free || nx->free || ny->free || nz->free) { + Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE); + analyzeStroke.layer = Canvas::Layer::FRONT; + Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke); + canvas->DrawLine(tail, tip, hcsAnalyze); + } + } } - if(dogd.drawing) - ssglDepthRangeLockToFront(false); - break; + return; } - case DISTANCE: - case DISTANCE_N_COPY: + case Type::DISTANCE: + case Type::DISTANCE_N_COPY: // These are used only as data structures, nothing to display. - break; + return; + + case Type::WORKPLANE: { + const Camera &camera = canvas->GetCamera(); - case WORKPLANE: { - Vector p; - p = SK.GetEntity(point[0])->PointGetNum(); + Vector p = SK.GetEntity(point[0])->PointGetNum(); + p = camera.AlignToPixelGrid(p); Vector u = Normal()->NormalU(); Vector v = Normal()->NormalV(); - double s = (min(SS.GW.width, SS.GW.height))*0.45/SS.GW.scale; + double s = (std::min(camera.width, camera.height)) * 0.45 / camera.scale; Vector us = u.ScaledBy(s); Vector vs = v.ScaledBy(s); @@ -653,72 +673,116 @@ void Entity::DrawOrGetDistance(void) { Vector mm = p.Minus(us).Minus(vs), mm2 = mm; Vector mp = p.Minus(us).Plus (vs); - if(dogd.drawing) { - ssglLineWidth(1); - ssglColorRGB(Style::Color(Style::NORMALS)); - glEnable(GL_LINE_STIPPLE); - glLineStipple(3, 0x1111); - } + Canvas::Stroke strokeBorder = stroke; + strokeBorder.zIndex -= 3; + strokeBorder.stipplePattern = StipplePattern::SHORT_DASH; + strokeBorder.stippleScale = 8.0; + Canvas::hStroke hcsBorder = canvas->GetStroke(strokeBorder); + + double textHeight = Style::TextHeight(hs) / camera.scale; if(!h.isFromRequest()) { - mm = mm.Plus(v.ScaledBy(70/SS.GW.scale)); - mm2 = mm2.Plus(u.ScaledBy(70/SS.GW.scale)); - LineDrawOrGetDistance(mm2, mm); + mm = mm.Plus(v.ScaledBy(textHeight * 4.7)); + mm2 = mm2.Plus(u.ScaledBy(textHeight * 4.7)); + canvas->DrawLine(mm2, mm, hcsBorder); } - LineDrawOrGetDistance(pp, pm); - LineDrawOrGetDistance(pm, mm2); - LineDrawOrGetDistance(mp, mm); - LineDrawOrGetDistance(pp, mp); - - if(dogd.drawing) - glDisable(GL_LINE_STIPPLE); - - std::string str = DescriptionString().substr(5); - double th = Style::DefaultTextHeight(); - if(dogd.drawing) { - Vector o = mm2.Plus(u.ScaledBy(3/SS.GW.scale)).Plus( - v.ScaledBy(3/SS.GW.scale)); - ssglWriteText(str, th, o, u, v, NULL, NULL); - } else { - Vector pos = mm2.Plus(u.ScaledBy(ssglStrWidth(str, th)/2)).Plus( - v.ScaledBy(ssglStrCapHeight(th)/2)); - Point2d pp = SS.GW.ProjectPoint(pos); - dogd.dmin = min(dogd.dmin, pp.DistanceTo(dogd.mp) - 10); - // If a line lies in a plane, then select the line, not - // the plane. - dogd.dmin += 3; + canvas->DrawLine(pp, pm, hcsBorder); + canvas->DrawLine(mm2, pm, hcsBorder); + canvas->DrawLine(mm, mp, hcsBorder); + canvas->DrawLine(pp, mp, hcsBorder); + + Vector o = mm2.Plus(u.ScaledBy(3.0 / camera.scale)).Plus( + v.ScaledBy(3.0 / camera.scale)); + std::string shortDesc = DescriptionString().substr(5); + canvas->DrawVectorText(shortDesc, textHeight, o, u, v, hcs); + return; + } + + case Type::LINE_SEGMENT: + case Type::CIRCLE: + case Type::ARC_OF_CIRCLE: + case Type::CUBIC: + case Type::CUBIC_PERIODIC: + case Type::TTF_TEXT: { + // Generate the rational polynomial curves, then piecewise linearize + // them, and display those. + if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcs)) { + canvas->DrawEdges(*GetOrGenerateEdges(), hcs); } - break; + if(type == Type::CIRCLE) { + Entity *dist = SK.GetEntity(distance); + if(dist->type == Type::DISTANCE) { + Param *p = SK.GetParam(dist->param[0]); + if(p->free) { + Canvas::Stroke analyzeStroke = Style::Stroke(Style::ANALYZE); + analyzeStroke.layer = Canvas::Layer::FRONT; + Canvas::hStroke hcsAnalyze = canvas->GetStroke(analyzeStroke); + if(!canvas->DrawBeziers(*GetOrGenerateBezierCurves(), hcsAnalyze)) { + canvas->DrawEdges(*GetOrGenerateEdges(), hcsAnalyze); + } + } + } + } + return; } + case Type::IMAGE: { + Canvas::Fill fill = {}; + std::shared_ptr pixmap; + switch(how) { + case DrawAs::HIDDEN: return; + + case DrawAs::HOVERED: { + fill.color = Style::Color(Style::HOVERED).WithAlpha(180); + fill.pattern = Canvas::FillPattern::CHECKERED_A; + fill.zIndex = 2; + break; + } - case LINE_SEGMENT: - case CIRCLE: - case ARC_OF_CIRCLE: - case CUBIC: - case CUBIC_PERIODIC: - case TTF_TEXT: - // Nothing but the curve(s). - break; + case DrawAs::SELECTED: { + fill.color = Style::Color(Style::SELECTED).WithAlpha(180); + fill.pattern = Canvas::FillPattern::CHECKERED_B; + fill.zIndex = 1; + break; + } - case FACE_NORMAL_PT: - case FACE_XPROD: - case FACE_N_ROT_TRANS: - case FACE_N_TRANS: - case FACE_N_ROT_AA: - // Do nothing; these are drawn with the triangle mesh - break; + default: + fill.color = RgbaColor::FromFloat(1.0f, 1.0f, 1.0f); + pixmap = SS.images[file]; + break; + } - default: - oops(); - } + Canvas::hFill hf = canvas->GetFill(fill); + Vector v[4] = {}; + for(int i = 0; i < 4; i++) { + v[i] = SK.GetEntity(point[i])->PointGetNum(); + } + Vector iu = v[3].Minus(v[0]); + Vector iv = v[1].Minus(v[0]); + + if(how == DrawAs::DEFAULT && pixmap == NULL) { + Canvas::Stroke stroke = Style::Stroke(Style::DRAW_ERROR); + stroke.color = stroke.color.WithAlpha(50); + Canvas::hStroke hs = canvas->GetStroke(stroke); + canvas->DrawLine(v[0], v[2], hs); + canvas->DrawLine(v[1], v[3], hs); + for(int i = 0; i < 4; i++) { + canvas->DrawLine(v[i], v[(i + 1) % 4], hs); + } + } else { + canvas->DrawPixmap(pixmap, v[0], iu, iv, + Point2d::From(0.0, 0.0), Point2d::From(1.0, 1.0), hf); + } + } - // And draw the curves; generate the rational polynomial curves for - // everything, then piecewise linearize them, and display those. - SEdgeList *sel = GetOrGenerateEdges(); - dogd.data = -1; - for(int i = 0; i < sel->l.n; i++) { - SEdge *se = &(sel->l.elem[i]); - LineDrawOrGetDistance(se->a, se->b, true, se->auxB); + case Type::FACE_NORMAL_PT: + case Type::FACE_XPROD: + case Type::FACE_N_ROT_TRANS: + case Type::FACE_N_TRANS: + case Type::FACE_N_ROT_AA: + case Type::FACE_ROT_NORMAL_PT: + case Type::FACE_N_ROT_AXIS_TRANS: + // Do nothing; these are drawn with the triangle mesh + return; } + ssassert(false, "Unexpected entity type"); } - diff --git a/src/dsc.h b/src/dsc.h index c61bbf5..93403bc 100644 --- a/src/dsc.h +++ b/src/dsc.h @@ -4,11 +4,39 @@ // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#ifndef __DSC_H -#define __DSC_H +#ifndef SOLVESPACE_DSC_H +#define SOLVESPACE_DSC_H #include "solvespace.h" +#include + +/// Trait indicating which types are handle types and should get the associated operators. +/// Specialize for each handle type and inherit from std::true_type. +template +struct IsHandleOracle : std::false_type {}; + +// Equality-compare any two instances of a handle type. +template +static inline typename std::enable_if::value, bool>::type +operator==(T const &lhs, T const &rhs) { + return lhs.v == rhs.v; +} + +// Inequality-compare any two instances of a handle type. +template +static inline typename std::enable_if::value, bool>::type +operator!=(T const &lhs, T const &rhs) { + return !(lhs == rhs); +} + +// Less-than-compare any two instances of a handle type. +template +static inline typename std::enable_if::value, bool>::type +operator<(T const &lhs, T const &rhs) { + return lhs.v < rhs.v; +} + class Vector; class Vector4; class Point2d; @@ -27,23 +55,23 @@ public: static Quaternion From(Vector u, Vector v); static Quaternion From(Vector axis, double dtheta); - Quaternion Plus(Quaternion b); - Quaternion Minus(Quaternion b); - Quaternion ScaledBy(double s); - double Magnitude(void); - Quaternion WithMagnitude(double s); + Quaternion Plus(Quaternion b) const; + Quaternion Minus(Quaternion b) const; + Quaternion ScaledBy(double s) const; + double Magnitude() const; + Quaternion WithMagnitude(double s) const; // Call a rotation matrix [ u' v' n' ]'; this returns the first and // second rows, where that matrix is generated by this quaternion - Vector RotationU(void); - Vector RotationV(void); - Vector RotationN(void); - Vector Rotate(Vector p); - - Quaternion ToThe(double p); - Quaternion Inverse(void); - Quaternion Times(Quaternion b); - Quaternion Mirror(void); + Vector RotationU() const; + Vector RotationV() const; + Vector RotationN() const; + Vector Rotate(Vector p) const; + + Quaternion ToThe(double p) const; + Quaternion Inverse() const; + Quaternion Times(Quaternion b) const; + Quaternion Mirror() const; }; class Vector { @@ -68,43 +96,117 @@ public: Vector pb, Vector db, double *ta, double *tb); - double Element(int i); - bool Equals(Vector v, double tol=LENGTH_EPS); - bool EqualsExactly(Vector v); - Vector Plus(Vector b); - Vector Minus(Vector b); - Vector Negated(void); - Vector Cross(Vector b); - double DirectionCosineWith(Vector b); - double Dot(Vector b); - Vector Normal(int which); - Vector RotatedAbout(Vector orig, Vector axis, double theta); - Vector RotatedAbout(Vector axis, double theta); - Vector DotInToCsys(Vector u, Vector v, Vector n); - Vector ScaleOutOfCsys(Vector u, Vector v, Vector n); - double DistanceToLine(Vector p0, Vector dp); - bool OnLineSegment(Vector a, Vector b, double tol=LENGTH_EPS); - Vector ClosestPointOnLine(Vector p0, Vector dp); - double Magnitude(void); - double MagSquared(void); - Vector WithMagnitude(double s); - Vector ScaledBy(double s); - Vector ProjectInto(hEntity wrkpl); - Vector ProjectVectorInto(hEntity wrkpl); - double DivPivoting(Vector delta); - Vector ClosestOrtho(void); - void MakeMaxMin(Vector *maxv, Vector *minv); - Vector ClampWithin(double minv, double maxv); + double Element(int i) const; + bool Equals(Vector v, double tol=LENGTH_EPS) const; + bool EqualsExactly(Vector v) const; + Vector Plus(Vector b) const; + Vector Minus(Vector b) const; + Vector Negated() const; + Vector Cross(Vector b) const; + double DirectionCosineWith(Vector b) const; + double Dot(Vector b) const; + Vector Normal(int which) const; + Vector RotatedAbout(Vector orig, Vector axis, double theta) const; + Vector RotatedAbout(Vector axis, double theta) const; + Vector DotInToCsys(Vector u, Vector v, Vector n) const; + Vector ScaleOutOfCsys(Vector u, Vector v, Vector n) const; + double DistanceToLine(Vector p0, Vector dp) const; + double DistanceToPlane(Vector normal, Vector origin) const; + bool OnLineSegment(Vector a, Vector b, double tol=LENGTH_EPS) const; + Vector ClosestPointOnLine(Vector p0, Vector deltal) const; + double Magnitude() const; + double MagSquared() const; + Vector WithMagnitude(double s) const; + Vector ScaledBy(double s) const; + Vector ProjectInto(hEntity wrkpl) const; + Vector ProjectVectorInto(hEntity wrkpl) const; + double DivProjected(Vector delta) const; + Vector ClosestOrtho() const; + void MakeMaxMin(Vector *maxv, Vector *minv) const; + Vector ClampWithin(double minv, double maxv) const; static bool BoundingBoxesDisjoint(Vector amax, Vector amin, Vector bmax, Vector bmin); static bool BoundingBoxIntersectsLine(Vector amax, Vector amin, - Vector p0, Vector p1, bool segment); - bool OutsideAndNotOn(Vector maxv, Vector minv); + Vector p0, Vector p1, bool asSegment); + bool OutsideAndNotOn(Vector maxv, Vector minv) const; Vector InPerspective(Vector u, Vector v, Vector n, - Vector origin, double cameraTan); - Point2d Project2d(Vector u, Vector v); - Point2d ProjectXy(void); - Vector4 Project4d(void); + Vector origin, double cameraTan) const; + Point2d Project2d(Vector u, Vector v) const; + Point2d ProjectXy() const; + Vector4 Project4d() const; +}; + +inline double Vector::Element(int i) const { + switch (i) { + case 0: return x; + case 1: return y; + case 2: return z; + default: ssassert(false, "Unexpected vector element index"); + } +} + +inline bool Vector::Equals(Vector v, double tol) const { + // Quick axis-aligned tests before going further + const Vector dv = this->Minus(v); + if (fabs(dv.x) > tol) return false; + if (fabs(dv.y) > tol) return false; + if (fabs(dv.z) > tol) return false; + + return dv.MagSquared() < tol*tol; +} + +inline Vector Vector::From(double x, double y, double z) { + return {x, y, z}; +} + +inline Vector Vector::Plus(Vector b) const { + return {x + b.x, y + b.y, z + b.z}; +} + +inline Vector Vector::Minus(Vector b) const { + return {x - b.x, y - b.y, z - b.z}; +} + +inline Vector Vector::Negated() const { + return {-x, -y, -z}; +} + +inline Vector Vector::Cross(Vector b) const { + return {-(z * b.y) + (y * b.z), (z * b.x) - (x * b.z), -(y * b.x) + (x * b.y)}; +} + +inline double Vector::Dot(Vector b) const { + return (x * b.x + y * b.y + z * b.z); +} + +inline double Vector::MagSquared() const { + return x * x + y * y + z * z; +} + +inline double Vector::Magnitude() const { + return sqrt(x * x + y * y + z * z); +} + +inline Vector Vector::ScaledBy(const double v) const { + return {x * v, y * v, z * v}; +} + +inline void Vector::MakeMaxMin(Vector *maxv, Vector *minv) const { + maxv->x = max(maxv->x, x); + maxv->y = max(maxv->y, y); + maxv->z = max(maxv->z, z); + + minv->x = min(minv->x, x); + minv->y = min(minv->y, y); + minv->z = min(minv->z, z); +} + +struct VectorHash { + size_t operator()(const Vector &v) const; +}; + +struct VectorPred { + bool operator()(Vector a, Vector b) const; }; class Vector4 { @@ -115,10 +217,10 @@ public: static Vector4 From(double w, Vector v3); static Vector4 Blend(Vector4 a, Vector4 b, double t); - Vector4 Plus(Vector4 b); - Vector4 Minus(Vector4 b); - Vector4 ScaledBy(double s); - Vector PerspectiveProject(void); + Vector4 Plus(Vector4 b) const; + Vector4 Minus(Vector4 b) const; + Vector4 ScaledBy(double s) const; + Vector PerspectiveProject() const; }; class Point2d { @@ -126,48 +228,61 @@ public: double x, y; static Point2d From(double x, double y); + static Point2d FromPolar(double r, double a); Point2d Plus(const Point2d &b) const; Point2d Minus(const Point2d &b) const; Point2d ScaledBy(double s) const; - double DivPivoting(Point2d delta) const; + double DivProjected(Point2d delta) const; double Dot(Point2d p) const; double DistanceTo(const Point2d &p) const; - double DistanceToLine(const Point2d &p0, const Point2d &dp, bool segment) const; - double Magnitude(void) const; - double MagSquared(void) const; + double DistanceToLine(const Point2d &p0, const Point2d &dp, bool asSegment) const; + double DistanceToLineSigned(const Point2d &p0, const Point2d &dp, bool asSegment) const; + double Angle() const; + double AngleTo(const Point2d &p) const; + double Magnitude() const; + double MagSquared() const; Point2d WithMagnitude(double v) const; - Point2d Normal(void) const; + Point2d Normal() const; bool Equals(Point2d v, double tol=LENGTH_EPS) const; }; // A simple list -template +template class List { + T *elem = nullptr; + int elemsAllocated = 0; + public: - T *elem; - int n; - int elemsAllocated; + int n = 0; - void AllocForOneMore(void) { - if(n >= elemsAllocated) { - elemsAllocated = (elemsAllocated + 32)*2; - T *newElem = (T *)MemAlloc((size_t)elemsAllocated*sizeof(elem[0])); + bool IsEmpty() const { return n == 0; } + + void ReserveMore(int howMuch) { + if(n + howMuch > elemsAllocated) { + elemsAllocated = n + howMuch; + T *newElem = (T *)::operator new[]((size_t)elemsAllocated*sizeof(T)); for(int i = 0; i < n; i++) { new(&newElem[i]) T(std::move(elem[i])); elem[i].~T(); } - MemFree(elem); + ::operator delete[](elem); elem = newElem; } } - void Add(T *t) { + void AllocForOneMore() { + if(n >= elemsAllocated) { + ReserveMore((elemsAllocated + 32)*2 - n); + } + } + + void Add(const T *t) { AllocForOneMore(); new(&elem[n++]) T(*t); } - void AddToBeginning(T *t) { + void AddToBeginning(const T *t) { AllocForOneMore(); new(&elem[n]) T(); std::move_backward(elem, elem + 1, elem + n + 1); @@ -175,58 +290,80 @@ public: n++; } - T *First(void) { - return (n == 0) ? NULL : &(elem[0]); + T *First() { + return IsEmpty() ? nullptr : &(elem[0]); + } + const T *First() const { + return IsEmpty() ? nullptr : &(elem[0]); } + + T *Last() { return IsEmpty() ? nullptr : &(elem[n - 1]); } + const T *Last() const { return IsEmpty() ? nullptr : &(elem[n - 1]); } + T *NextAfter(T *prev) { - if(!prev) return NULL; - if(prev - elem == (n - 1)) return NULL; + if(IsEmpty() || !prev) return NULL; + if(prev - First() == (n - 1)) return NULL; + return prev + 1; + } + const T *NextAfter(const T *prev) const { + if(IsEmpty() || !prev) return NULL; + if(prev - First() == (n - 1)) return NULL; return prev + 1; } - void ClearTags(void) { - int i; - for(i = 0; i < n; i++) { - elem[i].tag = 0; + T &Get(size_t i) { return elem[i]; } + T const &Get(size_t i) const { return elem[i]; } + T &operator[](size_t i) { return Get(i); } + T const &operator[](size_t i) const { return Get(i); } + + T *begin() { return IsEmpty() ? nullptr : &elem[0]; } + T *end() { return IsEmpty() ? nullptr : &elem[n]; } + const T *begin() const { return IsEmpty() ? nullptr : &elem[0]; } + const T *end() const { return IsEmpty() ? nullptr : &elem[n]; } + const T *cbegin() const { return begin(); } + const T *cend() const { return end(); } + + void ClearTags() { + for(auto & elt : *this) { + elt.tag = 0; } } - void Clear(void) { + void Clear() { for(int i = 0; i < n; i++) elem[i].~T(); - if(elem) MemFree(elem); + if(elem) ::operator delete[](elem); elem = NULL; n = elemsAllocated = 0; } - void RemoveTagged(void) { - int src, dest; - dest = 0; - for(src = 0; src < n; src++) { - if(elem[src].tag) { - // this item should be deleted - } else { - if(src != dest) { - elem[dest] = elem[src]; - } - dest++; + void RemoveTagged() { + auto newEnd = std::remove_if(this->begin(), this->end(), [](T &t) { + if(t.tag) { + return true; + } + return false; + }); + auto oldEnd = this->end(); + n = newEnd - begin(); + if (newEnd != nullptr && oldEnd != nullptr) { + while(newEnd != oldEnd) { + newEnd->~T(); + ++newEnd; } } - for(int i = dest; i < n; i++) - elem[i].~T(); - n = dest; // and elemsAllocated is untouched, because we didn't resize } void RemoveLast(int cnt) { - if(n < cnt) oops(); + ssassert(n >= cnt, "Removing more elements than the list contains"); for(int i = n - cnt; i < n; i++) elem[i].~T(); n -= cnt; // and elemsAllocated is untouched, same as in RemoveTagged } - void Reverse(void) { + void Reverse() { int i; for(i = 0; i < (n/2); i++) { swap(elem[i], elem[(n-1)-i]); @@ -234,139 +371,183 @@ public: } }; +// Comparison functor used by IdList and related classes +template +struct CompareId { + bool operator()(T const& lhs, T const& rhs) const { + return lhs.h.v < rhs.h.v; + } + bool operator()(T const& lhs, H rhs) const { + return lhs.h.v < rhs.v; + } +}; + // A list, where each element has an integer identifier. The list is kept // sorted by that identifier, and items can be looked up in log n time by // id. template class IdList { + T *elem = nullptr; + int elemsAllocated = 0; public: - T *elem; - int n; - int elemsAllocated; + int n = 0; - uint32_t MaximumId(void) { - uint32_t id = 0; + using Compare = CompareId; - int i; - for(i = 0; i < n; i++) { - id = max(id, elem[i].h.v); + bool IsEmpty() const { + return n == 0; + } + + void AllocForOneMore() { + if(n >= elemsAllocated) { + ReserveMore((elemsAllocated + 32)*2 - n); + } + } + + uint32_t MaximumId() { + if(IsEmpty()) { + return 0; + } else { + return Last()->h.v; } - return id; } H AddAndAssignId(T *t) { t->h.v = (MaximumId() + 1); - Add(t); + AllocForOneMore(); + + // Copy-construct at the end of the list. + new(&elem[n]) T(*t); + ++n; return t->h; } - void Add(T *t) { - if(n >= elemsAllocated) { - elemsAllocated = (elemsAllocated + 32)*2; - T *newElem = (T *)MemAlloc((size_t)elemsAllocated*sizeof(elem[0])); + T * LowerBound(T const& t) { + if(IsEmpty()) { + return nullptr; + } + auto it = std::lower_bound(begin(), end(), t, Compare()); + return it; + } + + T * LowerBound(H const& h) { + if(IsEmpty()) { + return nullptr; + } + auto it = std::lower_bound(begin(), end(), h, Compare()); + return it; + } + + int LowerBoundIndex(T const& t) { + if(IsEmpty()) { + return 0; + } + auto it = LowerBound(t); + auto idx = std::distance(begin(), it); + auto i = static_cast(idx); + return i; + } + void ReserveMore(int howMuch) { + if(n + howMuch > elemsAllocated) { + elemsAllocated = n + howMuch; + T *newElem = (T *)::operator new[]((size_t)elemsAllocated*sizeof(T)); for(int i = 0; i < n; i++) { new(&newElem[i]) T(std::move(elem[i])); elem[i].~T(); } - MemFree(elem); + ::operator delete[](elem); elem = newElem; } + } - int first = 0, last = n; - // We know that we must insert within the closed interval [first,last] - while(first != last) { - int mid = (first + last)/2; - H hm = elem[mid].h; - if(hm.v > t->h.v) { - last = mid; - } else if(hm.v < t->h.v) { - first = mid + 1; - } else { - dbp("can't insert in list; is handle %d not unique?", t->h.v); - oops(); - } - } + void Add(T *t) { + AllocForOneMore(); - int i = first; - new(&elem[n]) T(); - std::move_backward(elem + i, elem + n, elem + n + 1); - elem[i] = *t; - n++; + // Look to see if we already have something with the same handle value. + ssassert(FindByIdNoOops(t->h) == nullptr, "Handle isn't unique"); + + // Copy-construct at the end of the list. + new(&elem[n]) T(*t); + ++n; + // The item we just added is trivially sorted, so "merge" + std::inplace_merge(begin(), end() - 1, end(), Compare()); } T *FindById(H h) { T *t = FindByIdNoOops(h); - if(!t) { - dbp("failed to look up item %08x, searched %d items", h.v, n); - oops(); - } + ssassert(t != NULL, "Cannot find handle"); return t; } int IndexOf(H h) { - int first = 0, last = n-1; - while(first <= last) { - int mid = (first + last)/2; - H hm = elem[mid].h; - if(hm.v > h.v) { - last = mid-1; // and first stays the same - } else if(hm.v < h.v) { - first = mid+1; // and last stays the same - } else { - return mid; - } + if(IsEmpty()) { + return -1; + } + auto it = LowerBound(h); + auto idx = std::distance(begin(), it); + if (idx < n) { + return idx; } return -1; } T *FindByIdNoOops(H h) { - int first = 0, last = n-1; - while(first <= last) { - int mid = (first + last)/2; - H hm = elem[mid].h; - if(hm.v > h.v) { - last = mid-1; // and first stays the same - } else if(hm.v < h.v) { - first = mid+1; // and last stays the same - } else { - return &(elem[mid]); - } + if(IsEmpty()) { + return nullptr; + } + auto it = LowerBound(h); + if (it == nullptr || it == end()) { + return nullptr; + } + if (it->h.v == h.v) { + return it; } - return NULL; + return nullptr; } - T *First(void) { - return (n == 0) ? NULL : &(elem[0]); + T *First() { + return (IsEmpty()) ? NULL : &(elem[0]); + } + T *Last() { + return (IsEmpty()) ? NULL : &(elem[n-1]); } T *NextAfter(T *prev) { - if(!prev) return NULL; - if(prev - elem == (n - 1)) return NULL; + if(IsEmpty() || !prev) return NULL; + if(prev - First() == (n - 1)) return NULL; return prev + 1; } - void ClearTags(void) { - int i; - for(i = 0; i < n; i++) { - elem[i].tag = 0; - } + T &Get(size_t i) { return elem[i]; } + T const &Get(size_t i) const { return elem[i]; } + T &operator[](size_t i) { return Get(i); } + T const &operator[](size_t i) const { return Get(i); } + + T *begin() { return IsEmpty() ? nullptr : &elem[0]; } + T *end() { return IsEmpty() ? nullptr : &elem[0] + n; } + const T *begin() const { return IsEmpty() ? nullptr : &elem[0]; } + const T *end() const { return IsEmpty() ? nullptr : &elem[0] + n; } + const T *cbegin() const { return begin(); } + const T *cend() const { return end(); } + + void ClearTags() { + for(auto &elt : *this) { elt.tag = 0; } } void Tag(H h, int tag) { - int i; - for(i = 0; i < n; i++) { - if(elem[i].h.v == h.v) { - elem[i].tag = tag; - } + auto it = FindByIdNoOops(h); + if (it != nullptr) { + it->tag = tag; } } - void RemoveTagged(void) { + void RemoveTagged() { int src, dest; dest = 0; for(src = 0; src < n; src++) { if(elem[src].tag) { // this item should be deleted + elem[src].Clear(); } else { if(src != dest) { elem[dest] = elem[src]; @@ -387,28 +568,28 @@ public: void MoveSelfInto(IdList *l) { l->Clear(); - *l = *this; - elemsAllocated = n = 0; - elem = NULL; + std::swap(l->elem, elem); + std::swap(l->elemsAllocated, elemsAllocated); + std::swap(l->n, n); } void DeepCopyInto(IdList *l) { l->Clear(); - l->elem = (T *)MemAlloc(elemsAllocated * sizeof(elem[0])); + l->elem = (T *)::operator new[](elemsAllocated * sizeof(elem[0])); for(int i = 0; i < n; i++) new(&l->elem[i]) T(elem[i]); l->elemsAllocated = elemsAllocated; l->n = n; } - void Clear(void) { + void Clear() { for(int i = 0; i < n; i++) { elem[i].Clear(); elem[i].~T(); } - elemsAllocated = n = 0; - if(elem) MemFree(elem); + if(elem) ::operator delete[](elem); elem = NULL; + elemsAllocated = n = 0; } }; @@ -426,7 +607,7 @@ public: double X[MAX_UNKNOWNS]; int n; - void Solve(void); + void Solve(); }; #define RGBi(r, g, b) RgbaColor::From((r), (g), (b)) @@ -438,10 +619,12 @@ class RgbaColor { public: uint8_t red, green, blue, alpha; - float redF(void) const { return (float)red / 255.0f; } - float greenF(void) const { return (float)green / 255.0f; } - float blueF(void) const { return (float)blue / 255.0f; } - float alphaF(void) const { return (float)alpha / 255.0f; } + float redF() const { return (float)red / 255.0f; } + float greenF() const { return (float)green / 255.0f; } + float blueF() const { return (float)blue / 255.0f; } + float alphaF() const { return (float)alpha / 255.0f; } + + bool IsEmpty() const { return alpha == 0; } bool Equals(RgbaColor c) const { return @@ -451,7 +634,13 @@ public: c.alpha == alpha; } - uint32_t ToPackedIntBGRA(void) const { + RgbaColor WithAlpha(uint8_t newAlpha) const { + RgbaColor color = *this; + color.alpha = newAlpha; + return color; + } + + uint32_t ToPackedIntBGRA() const { return blue | (uint32_t)(green << 8) | @@ -459,7 +648,7 @@ public: (uint32_t)((255 - alpha) << 24); } - uint32_t ToPackedInt(void) const { + uint32_t ToPackedInt() const { return red | (uint32_t)(green << 8) | @@ -467,7 +656,7 @@ public: (uint32_t)((255 - alpha) << 24); } - uint32_t ToARGB32(void) const { + uint32_t ToARGB32() const { return blue | (uint32_t)(green << 8) | @@ -507,7 +696,12 @@ public: (int)((bgra) & 0xff), (int)(255 - ((bgra >> 24) & 0xff))); } +}; +struct RgbaColorCompare { + bool operator()(RgbaColor a, RgbaColor b) const { + return a.ToARGB32() < b.ToARGB32(); + } }; class BBox { @@ -517,12 +711,12 @@ public: static BBox From(const Vector &p0, const Vector &p1); - Vector GetOrigin(); - Vector GetExtents(); + Vector GetOrigin() const; + Vector GetExtents() const; void Include(const Vector &v, double r = 0.0); - bool Overlaps(BBox &b1); - bool Contains(const Point2d &p); + bool Overlaps(const BBox &b1) const; + bool Contains(const Point2d &p, double r = 0.0) const; }; #endif diff --git a/src/entity.cpp b/src/entity.cpp index 7f4a3a7..a0f2f94 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -10,14 +10,14 @@ const hEntity EntityBase::FREE_IN_3D = { 0 }; const hEntity EntityBase::NO_ENTITY = { 0 }; -bool EntityBase::HasVector(void) { +bool EntityBase::HasVector() const { switch(type) { - case LINE_SEGMENT: - case NORMAL_IN_3D: - case NORMAL_IN_2D: - case NORMAL_N_COPY: - case NORMAL_N_ROT: - case NORMAL_N_ROT_AA: + case Type::LINE_SEGMENT: + case Type::NORMAL_IN_3D: + case Type::NORMAL_IN_2D: + case Type::NORMAL_N_COPY: + case Type::NORMAL_N_ROT: + case Type::NORMAL_N_ROT_AA: return true; default: @@ -25,97 +25,116 @@ bool EntityBase::HasVector(void) { } } -ExprVector EntityBase::VectorGetExprs(void) { +ExprVector EntityBase::VectorGetExprsInWorkplane(hEntity wrkpl) const { switch(type) { - case LINE_SEGMENT: - return (SK.GetEntity(point[0])->PointGetExprs()).Minus( - SK.GetEntity(point[1])->PointGetExprs()); - - case NORMAL_IN_3D: - case NORMAL_IN_2D: - case NORMAL_N_COPY: - case NORMAL_N_ROT: - case NORMAL_N_ROT_AA: - return NormalExprsN(); - - default: oops(); + case Type::LINE_SEGMENT: + return (SK.GetEntity(point[0])->PointGetExprsInWorkplane(wrkpl)).Minus( + SK.GetEntity(point[1])->PointGetExprsInWorkplane(wrkpl)); + + case Type::NORMAL_IN_3D: + case Type::NORMAL_IN_2D: + case Type::NORMAL_N_COPY: + case Type::NORMAL_N_ROT: + case Type::NORMAL_N_ROT_AA: { + ExprVector ev = NormalExprsN(); + if(wrkpl == EntityBase::FREE_IN_3D) { + return ev; + } + // Get the offset and basis vectors for this weird exotic csys. + EntityBase *w = SK.GetEntity(wrkpl); + ExprVector wu = w->Normal()->NormalExprsU(); + ExprVector wv = w->Normal()->NormalExprsV(); + + // Get our coordinates in three-space, and project them into that + // coordinate system. + ExprVector result; + result.x = ev.Dot(wu); + result.y = ev.Dot(wv); + result.z = Expr::From(0.0); + return result; + } + default: ssassert(false, "Unexpected entity type"); } } -Vector EntityBase::VectorGetNum(void) { +ExprVector EntityBase::VectorGetExprs() const { + return VectorGetExprsInWorkplane(EntityBase::FREE_IN_3D); +} + +Vector EntityBase::VectorGetNum() const { switch(type) { - case LINE_SEGMENT: + case Type::LINE_SEGMENT: return (SK.GetEntity(point[0])->PointGetNum()).Minus( SK.GetEntity(point[1])->PointGetNum()); - case NORMAL_IN_3D: - case NORMAL_IN_2D: - case NORMAL_N_COPY: - case NORMAL_N_ROT: - case NORMAL_N_ROT_AA: + case Type::NORMAL_IN_3D: + case Type::NORMAL_IN_2D: + case Type::NORMAL_N_COPY: + case Type::NORMAL_N_ROT: + case Type::NORMAL_N_ROT_AA: return NormalN(); - default: oops(); + default: ssassert(false, "Unexpected entity type"); } } -Vector EntityBase::VectorGetRefPoint(void) { +Vector EntityBase::VectorGetRefPoint() const { switch(type) { - case LINE_SEGMENT: + case Type::LINE_SEGMENT: return ((SK.GetEntity(point[0])->PointGetNum()).Plus( SK.GetEntity(point[1])->PointGetNum())).ScaledBy(0.5); - case NORMAL_IN_3D: - case NORMAL_IN_2D: - case NORMAL_N_COPY: - case NORMAL_N_ROT: - case NORMAL_N_ROT_AA: + case Type::NORMAL_IN_3D: + case Type::NORMAL_IN_2D: + case Type::NORMAL_N_COPY: + case Type::NORMAL_N_ROT: + case Type::NORMAL_N_ROT_AA: return SK.GetEntity(point[0])->PointGetNum(); - default: oops(); + default: ssassert(false, "Unexpected entity type"); } } -Vector EntityBase::VectorGetStartPoint(void) { +Vector EntityBase::VectorGetStartPoint() const { switch(type) { - case LINE_SEGMENT: + case Type::LINE_SEGMENT: return SK.GetEntity(point[1])->PointGetNum(); - case NORMAL_IN_3D: - case NORMAL_IN_2D: - case NORMAL_N_COPY: - case NORMAL_N_ROT: - case NORMAL_N_ROT_AA: + case Type::NORMAL_IN_3D: + case Type::NORMAL_IN_2D: + case Type::NORMAL_N_COPY: + case Type::NORMAL_N_ROT: + case Type::NORMAL_N_ROT_AA: return SK.GetEntity(point[0])->PointGetNum(); - default: oops(); + default: ssassert(false, "Unexpected entity type"); } } -bool EntityBase::IsCircle(void) { - return (type == CIRCLE) || (type == ARC_OF_CIRCLE); +bool EntityBase::IsCircle() const { + return (type == Type::CIRCLE) || (type == Type::ARC_OF_CIRCLE); } -Expr *EntityBase::CircleGetRadiusExpr(void) { - if(type == CIRCLE) { +Expr *EntityBase::CircleGetRadiusExpr() const { + if(type == Type::CIRCLE) { return SK.GetEntity(distance)->DistanceGetExpr(); - } else if(type == ARC_OF_CIRCLE) { + } else if(type == Type::ARC_OF_CIRCLE) { return Constraint::Distance(workplane, point[0], point[1]); - } else oops(); + } else ssassert(false, "Unexpected entity type"); } -double EntityBase::CircleGetRadiusNum(void) { - if(type == CIRCLE) { +double EntityBase::CircleGetRadiusNum() const { + if(type == Type::CIRCLE) { return SK.GetEntity(distance)->DistanceGetNum(); - } else if(type == ARC_OF_CIRCLE) { + } else if(type == Type::ARC_OF_CIRCLE) { Vector c = SK.GetEntity(point[0])->PointGetNum(); Vector pa = SK.GetEntity(point[1])->PointGetNum(); return (pa.Minus(c)).Magnitude(); - } else oops(); + } else ssassert(false, "Unexpected entity type"); } -void EntityBase::ArcGetAngles(double *thetaa, double *thetab, double *dtheta) { - if(type != ARC_OF_CIRCLE) oops(); +void EntityBase::ArcGetAngles(double *thetaa, double *thetab, double *dtheta) const { + ssassert(type == Type::ARC_OF_CIRCLE, "Unexpected entity type"); Quaternion q = Normal()->NormalGetNum(); Vector u = q.RotationU(), v = q.RotationV(); @@ -137,47 +156,47 @@ void EntityBase::ArcGetAngles(double *thetaa, double *thetab, double *dtheta) { while(*dtheta > (2*PI)) *dtheta -= 2*PI; } -Vector EntityBase::CubicGetStartNum(void) { +Vector EntityBase::CubicGetStartNum() const { return SK.GetEntity(point[0])->PointGetNum(); } -Vector EntityBase::CubicGetFinishNum(void) { +Vector EntityBase::CubicGetFinishNum() const { return SK.GetEntity(point[3+extraPoints])->PointGetNum(); } -ExprVector EntityBase::CubicGetStartTangentExprs(void) { +ExprVector EntityBase::CubicGetStartTangentExprs() const { ExprVector pon = SK.GetEntity(point[0])->PointGetExprs(), poff = SK.GetEntity(point[1])->PointGetExprs(); return (pon.Minus(poff)); } -ExprVector EntityBase::CubicGetFinishTangentExprs(void) { +ExprVector EntityBase::CubicGetFinishTangentExprs() const { ExprVector pon = SK.GetEntity(point[3+extraPoints])->PointGetExprs(), poff = SK.GetEntity(point[2+extraPoints])->PointGetExprs(); return (pon.Minus(poff)); } -Vector EntityBase::CubicGetStartTangentNum(void) { +Vector EntityBase::CubicGetStartTangentNum() const { Vector pon = SK.GetEntity(point[0])->PointGetNum(), poff = SK.GetEntity(point[1])->PointGetNum(); return (pon.Minus(poff)); } -Vector EntityBase::CubicGetFinishTangentNum(void) { +Vector EntityBase::CubicGetFinishTangentNum() const { Vector pon = SK.GetEntity(point[3+extraPoints])->PointGetNum(), poff = SK.GetEntity(point[2+extraPoints])->PointGetNum(); return (pon.Minus(poff)); } -bool EntityBase::IsWorkplane(void) { - return (type == WORKPLANE); +bool EntityBase::IsWorkplane() const { + return (type == Type::WORKPLANE); } -ExprVector EntityBase::WorkplaneGetOffsetExprs(void) { +ExprVector EntityBase::WorkplaneGetOffsetExprs() const { return SK.GetEntity(point[0])->PointGetExprs(); } -Vector EntityBase::WorkplaneGetOffset(void) { +Vector EntityBase::WorkplaneGetOffset() const { return SK.GetEntity(point[0])->PointGetNum(); } -void EntityBase::WorkplaneGetPlaneExprs(ExprVector *n, Expr **dn) { - if(type == WORKPLANE) { +void EntityBase::WorkplaneGetPlaneExprs(ExprVector *n, Expr **dn) const { + if(type == Type::WORKPLANE) { *n = Normal()->NormalExprsN(); ExprVector p0 = SK.GetEntity(point[0])->PointGetExprs(); @@ -185,49 +204,48 @@ void EntityBase::WorkplaneGetPlaneExprs(ExprVector *n, Expr **dn) { // n dot p - n dot p0 = 0 // so dn = n dot p0 *dn = p0.Dot(*n); - } else { - oops(); - } + } else ssassert(false, "Unexpected entity type"); } -bool EntityBase::IsDistance(void) { - return (type == DISTANCE) || - (type == DISTANCE_N_COPY); +bool EntityBase::IsDistance() const { + return (type == Type::DISTANCE) || + (type == Type::DISTANCE_N_COPY); } -double EntityBase::DistanceGetNum(void) { - if(type == DISTANCE) { +double EntityBase::DistanceGetNum() const { + if(type == Type::DISTANCE) { return SK.GetParam(param[0])->val; - } else if(type == DISTANCE_N_COPY) { + } else if(type == Type::DISTANCE_N_COPY) { return numDistance; - } else oops(); + } else ssassert(false, "Unexpected entity type"); } -Expr *EntityBase::DistanceGetExpr(void) { - if(type == DISTANCE) { +Expr *EntityBase::DistanceGetExpr() const { + if(type == Type::DISTANCE) { return Expr::From(param[0]); - } else if(type == DISTANCE_N_COPY) { + } else if(type == Type::DISTANCE_N_COPY) { return Expr::From(numDistance); - } else oops(); + } else ssassert(false, "Unexpected entity type"); } void EntityBase::DistanceForceTo(double v) { - if(type == DISTANCE) { + if(type == Type::DISTANCE) { (SK.GetParam(param[0]))->val = v; - } else if(type == DISTANCE_N_COPY) { + } else if(type == Type::DISTANCE_N_COPY) { // do nothing, it's locked - } else oops(); + } else ssassert(false, "Unexpected entity type"); } -EntityBase *EntityBase::Normal(void) { +EntityBase *EntityBase::Normal() const { return SK.GetEntity(normal); } -bool EntityBase::IsPoint(void) { +bool EntityBase::IsPoint() const { switch(type) { - case POINT_IN_3D: - case POINT_IN_2D: - case POINT_N_COPY: - case POINT_N_TRANS: - case POINT_N_ROT_TRANS: - case POINT_N_ROT_AA: + case Type::POINT_IN_3D: + case Type::POINT_IN_2D: + case Type::POINT_N_COPY: + case Type::POINT_N_TRANS: + case Type::POINT_N_ROT_TRANS: + case Type::POINT_N_ROT_AA: + case Type::POINT_N_ROT_AXIS_TRANS: return true; default: @@ -235,66 +253,66 @@ bool EntityBase::IsPoint(void) { } } -bool EntityBase::IsNormal(void) { +bool EntityBase::IsNormal() const { switch(type) { - case NORMAL_IN_3D: - case NORMAL_IN_2D: - case NORMAL_N_COPY: - case NORMAL_N_ROT: - case NORMAL_N_ROT_AA: + case Type::NORMAL_IN_3D: + case Type::NORMAL_IN_2D: + case Type::NORMAL_N_COPY: + case Type::NORMAL_N_ROT: + case Type::NORMAL_N_ROT_AA: return true; default: return false; } } -Quaternion EntityBase::NormalGetNum(void) { +Quaternion EntityBase::NormalGetNum() const { Quaternion q; switch(type) { - case NORMAL_IN_3D: + case Type::NORMAL_IN_3D: q = Quaternion::From(param[0], param[1], param[2], param[3]); break; - case NORMAL_IN_2D: { + case Type::NORMAL_IN_2D: { EntityBase *wrkpl = SK.GetEntity(workplane); EntityBase *norm = SK.GetEntity(wrkpl->normal); q = norm->NormalGetNum(); break; } - case NORMAL_N_COPY: + case Type::NORMAL_N_COPY: q = numNormal; break; - case NORMAL_N_ROT: + case Type::NORMAL_N_ROT: q = Quaternion::From(param[0], param[1], param[2], param[3]); q = q.Times(numNormal); break; - case NORMAL_N_ROT_AA: { + case Type::NORMAL_N_ROT_AA: { q = GetAxisAngleQuaternion(0); q = q.Times(numNormal); break; } - default: oops(); + default: ssassert(false, "Unexpected entity type"); } return q; } void EntityBase::NormalForceTo(Quaternion q) { switch(type) { - case NORMAL_IN_3D: + case Type::NORMAL_IN_3D: SK.GetParam(param[0])->val = q.w; SK.GetParam(param[1])->val = q.vx; SK.GetParam(param[2])->val = q.vy; SK.GetParam(param[3])->val = q.vz; break; - case NORMAL_IN_2D: - case NORMAL_N_COPY: + case Type::NORMAL_IN_2D: + case Type::NORMAL_N_COPY: // There's absolutely nothing to do; these are locked. break; - case NORMAL_N_ROT: { + case Type::NORMAL_N_ROT: { Quaternion qp = q.Times(numNormal.Inverse()); SK.GetParam(param[0])->val = qp.w; @@ -304,52 +322,52 @@ void EntityBase::NormalForceTo(Quaternion q) { break; } - case NORMAL_N_ROT_AA: + case Type::NORMAL_N_ROT_AA: // Not sure if I'll bother implementing this one break; - default: oops(); + default: ssassert(false, "Unexpected entity type"); } } -Vector EntityBase::NormalU(void) { +Vector EntityBase::NormalU() const { return NormalGetNum().RotationU(); } -Vector EntityBase::NormalV(void) { +Vector EntityBase::NormalV() const { return NormalGetNum().RotationV(); } -Vector EntityBase::NormalN(void) { +Vector EntityBase::NormalN() const { return NormalGetNum().RotationN(); } -ExprVector EntityBase::NormalExprsU(void) { +ExprVector EntityBase::NormalExprsU() const { return NormalGetExprs().RotationU(); } -ExprVector EntityBase::NormalExprsV(void) { +ExprVector EntityBase::NormalExprsV() const { return NormalGetExprs().RotationV(); } -ExprVector EntityBase::NormalExprsN(void) { +ExprVector EntityBase::NormalExprsN() const { return NormalGetExprs().RotationN(); } -ExprQuaternion EntityBase::NormalGetExprs(void) { +ExprQuaternion EntityBase::NormalGetExprs() const { ExprQuaternion q; switch(type) { - case NORMAL_IN_3D: + case Type::NORMAL_IN_3D: q = ExprQuaternion::From(param[0], param[1], param[2], param[3]); break; - case NORMAL_IN_2D: { + case Type::NORMAL_IN_2D: { EntityBase *wrkpl = SK.GetEntity(workplane); EntityBase *norm = SK.GetEntity(wrkpl->normal); q = norm->NormalGetExprs(); break; } - case NORMAL_N_COPY: + case Type::NORMAL_N_COPY: q = ExprQuaternion::From(numNormal); break; - case NORMAL_N_ROT: { + case Type::NORMAL_N_ROT: { ExprQuaternion orig = ExprQuaternion::From(numNormal); q = ExprQuaternion::From(param[0], param[1], param[2], param[3]); @@ -357,27 +375,44 @@ ExprQuaternion EntityBase::NormalGetExprs(void) { break; } - case NORMAL_N_ROT_AA: { + case Type::NORMAL_N_ROT_AA: { ExprQuaternion orig = ExprQuaternion::From(numNormal); q = GetAxisAngleQuaternionExprs(0); q = q.Times(orig); break; } - default: oops(); + default: ssassert(false, "Unexpected entity type"); } return q; } +void EntityBase::PointForceParamTo(Vector p) { + switch(type) { + case Type::POINT_IN_3D: + SK.GetParam(param[0])->val = p.x; + SK.GetParam(param[1])->val = p.y; + SK.GetParam(param[2])->val = p.z; + break; + + case Type::POINT_IN_2D: + SK.GetParam(param[0])->val = p.x; + SK.GetParam(param[1])->val = p.y; + break; + + default: ssassert(false, "Unexpected entity type"); + } +} + void EntityBase::PointForceTo(Vector p) { switch(type) { - case POINT_IN_3D: + case Type::POINT_IN_3D: SK.GetParam(param[0])->val = p.x; SK.GetParam(param[1])->val = p.y; SK.GetParam(param[2])->val = p.z; break; - case POINT_IN_2D: { + case Type::POINT_IN_2D: { EntityBase *c = SK.GetEntity(workplane); p = p.Minus(c->WorkplaneGetOffset()); SK.GetParam(param[0])->val = p.Dot(c->Normal()->NormalU()); @@ -385,7 +420,7 @@ void EntityBase::PointForceTo(Vector p) { break; } - case POINT_N_TRANS: { + case Type::POINT_N_TRANS: { if(timesApplied == 0) break; Vector trans = (p.Minus(numPoint)).ScaledBy(1.0/timesApplied); SK.GetParam(param[0])->val = trans.x; @@ -394,7 +429,7 @@ void EntityBase::PointForceTo(Vector p) { break; } - case POINT_N_ROT_TRANS: { + case Type::POINT_N_ROT_TRANS: { // Force only the translation; leave the rotation unchanged. But // remember that we're working with respect to the rotated // point. @@ -405,7 +440,7 @@ void EntityBase::PointForceTo(Vector p) { break; } - case POINT_N_ROT_AA: { + case Type::POINT_N_ROT_AA: { // Force only the angle; the axis and center of rotation stay Vector offset = Vector::From(param[0], param[1], param[2]); Vector normal = Vector::From(param[4], param[5], param[6]); @@ -420,26 +455,54 @@ void EntityBase::PointForceTo(Vector p) { // in order to avoid jumps when you cross from +pi to -pi while(dtheta < -PI) dtheta += 2*PI; while(dtheta > PI) dtheta -= 2*PI; + // this extra *2 explains the mystery *4 SK.GetParam(param[3])->val = (thetai + dtheta)/(timesApplied*2); break; } - case POINT_N_COPY: + case Type::POINT_N_ROT_AXIS_TRANS: { + if(timesApplied == 0) break; + // is the point on the rotation axis? + Vector offset = Vector::From(param[0], param[1], param[2]); + Vector normal = Vector::From(param[4], param[5], param[6]).WithMagnitude(1.0); + Vector check = numPoint.Minus(offset).Cross(normal); + if (check.Dot(check) < LENGTH_EPS) { // if so, do extrusion style drag + Vector trans = (p.Minus(numPoint)); + SK.GetParam(param[7])->val = trans.Dot(normal)/timesApplied; + } else { // otherwise do rotation style + Vector u = normal.Normal(0), v = normal.Normal(1); + Vector po = p.Minus(offset), numo = numPoint.Minus(offset); + double thetap = atan2(v.Dot(po), u.Dot(po)); + double thetan = atan2(v.Dot(numo), u.Dot(numo)); + double thetaf = (thetap - thetan); + double thetai = (SK.GetParam(param[3])->val)*timesApplied*2; + double dtheta = thetaf - thetai; + // Take the smallest possible change in the actual step angle, + // in order to avoid jumps when you cross from +pi to -pi + while(dtheta < -PI) dtheta += 2*PI; + while(dtheta > PI) dtheta -= 2*PI; + // this extra *2 explains the mystery *4 + SK.GetParam(param[3])->val = (thetai + dtheta)/(timesApplied*2); + } + break; + } + + case Type::POINT_N_COPY: // Nothing to do; it's a static copy break; - default: oops(); + default: ssassert(false, "Unexpected entity type"); } } -Vector EntityBase::PointGetNum(void) { +Vector EntityBase::PointGetNum() const { Vector p; switch(type) { - case POINT_IN_3D: + case Type::POINT_IN_3D: p = Vector::From(param[0], param[1], param[2]); break; - case POINT_IN_2D: { + case Type::POINT_IN_2D: { EntityBase *c = SK.GetEntity(workplane); Vector u = c->Normal()->NormalU(); Vector v = c->Normal()->NormalV(); @@ -449,13 +512,13 @@ Vector EntityBase::PointGetNum(void) { break; } - case POINT_N_TRANS: { + case Type::POINT_N_TRANS: { Vector trans = Vector::From(param[0], param[1], param[2]); p = numPoint.Plus(trans.ScaledBy(timesApplied)); break; } - case POINT_N_ROT_TRANS: { + case Type::POINT_N_ROT_TRANS: { Vector offset = Vector::From(param[0], param[1], param[2]); Quaternion q = PointGetQuaternion(); p = q.Rotate(numPoint); @@ -463,7 +526,7 @@ Vector EntityBase::PointGetNum(void) { break; } - case POINT_N_ROT_AA: { + case Type::POINT_N_ROT_AA: { Vector offset = Vector::From(param[0], param[1], param[2]); Quaternion q = PointGetQuaternion(); p = numPoint.Minus(offset); @@ -472,23 +535,34 @@ Vector EntityBase::PointGetNum(void) { break; } - case POINT_N_COPY: + case Type::POINT_N_ROT_AXIS_TRANS: { + Vector offset = Vector::From(param[0], param[1], param[2]); + Vector displace = Vector::From(param[4], param[5], param[6]) + .WithMagnitude(SK.GetParam(param[7])->val).ScaledBy(timesApplied); + Quaternion q = PointGetQuaternion(); + p = numPoint.Minus(offset); + p = q.Rotate(p); + p = p.Plus(offset).Plus(displace); + break; + } + + case Type::POINT_N_COPY: p = numPoint; break; - default: oops(); + default: ssassert(false, "Unexpected entity type"); } return p; } -ExprVector EntityBase::PointGetExprs(void) { +ExprVector EntityBase::PointGetExprs() const { ExprVector r; switch(type) { - case POINT_IN_3D: + case Type::POINT_IN_3D: r = ExprVector::From(param[0], param[1], param[2]); break; - case POINT_IN_2D: { + case Type::POINT_IN_2D: { EntityBase *c = SK.GetEntity(workplane); ExprVector u = c->Normal()->NormalExprsU(); ExprVector v = c->Normal()->NormalExprsV(); @@ -497,13 +571,13 @@ ExprVector EntityBase::PointGetExprs(void) { r = r.Plus(v.ScaledBy(Expr::From(param[1]))); break; } - case POINT_N_TRANS: { + case Type::POINT_N_TRANS: { ExprVector orig = ExprVector::From(numPoint); ExprVector trans = ExprVector::From(param[0], param[1], param[2]); r = orig.Plus(trans.ScaledBy(Expr::From(timesApplied))); break; } - case POINT_N_ROT_TRANS: { + case Type::POINT_N_ROT_TRANS: { ExprVector orig = ExprVector::From(numPoint); ExprVector trans = ExprVector::From(param[0], param[1], param[2]); ExprQuaternion q = @@ -512,7 +586,7 @@ ExprVector EntityBase::PointGetExprs(void) { r = orig.Plus(trans); break; } - case POINT_N_ROT_AA: { + case Type::POINT_N_ROT_AA: { ExprVector orig = ExprVector::From(numPoint); ExprVector trans = ExprVector::From(param[0], param[1], param[2]); ExprQuaternion q = GetAxisAngleQuaternionExprs(3); @@ -521,17 +595,29 @@ ExprVector EntityBase::PointGetExprs(void) { r = orig.Plus(trans); break; } - case POINT_N_COPY: + case Type::POINT_N_ROT_AXIS_TRANS: { + ExprVector orig = ExprVector::From(numPoint); + ExprVector trans = ExprVector::From(param[0], param[1], param[2]); + ExprVector displace = ExprVector::From(param[4], param[5], param[6]) + .WithMagnitude(Expr::From(1.0)).ScaledBy(Expr::From(timesApplied)).ScaledBy(Expr::From(param[7])); + + ExprQuaternion q = GetAxisAngleQuaternionExprs(3); + orig = orig.Minus(trans); + orig = q.Rotate(orig); + r = orig.Plus(trans).Plus(displace); + break; + } + case Type::POINT_N_COPY: r = ExprVector::From(numPoint); break; - default: oops(); + default: ssassert(false, "Unexpected entity type"); } return r; } -void EntityBase::PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) { - if(type == POINT_IN_2D && workplane.v == wrkpl.v) { +void EntityBase::PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) const { + if(type == Type::POINT_IN_2D && workplane == wrkpl) { // They want our coordinates in the form that we've written them, // very nice. *u = Expr::From(param[0]); @@ -552,8 +638,19 @@ void EntityBase::PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) { } } +ExprVector EntityBase::PointGetExprsInWorkplane(hEntity wrkpl) const { + if(wrkpl == Entity::FREE_IN_3D) { + return PointGetExprs(); + } + + ExprVector r; + PointGetExprsInWorkplane(wrkpl, &r.x, &r.y); + r.z = Expr::From(0.0); + return r; +} + void EntityBase::PointForceQuaternionTo(Quaternion q) { - if(type != POINT_N_ROT_TRANS) oops(); + ssassert(type == Type::POINT_N_ROT_TRANS, "Unexpected entity type"); SK.GetParam(param[3])->val = q.w; SK.GetParam(param[4])->val = q.vx; @@ -561,7 +658,7 @@ void EntityBase::PointForceQuaternionTo(Quaternion q) { SK.GetParam(param[6])->val = q.vz; } -Quaternion EntityBase::GetAxisAngleQuaternion(int param0) { +Quaternion EntityBase::GetAxisAngleQuaternion(int param0) const { Quaternion q; double theta = timesApplied*SK.GetParam(param[param0+0])->val; double s = sin(theta), c = cos(theta); @@ -572,7 +669,7 @@ Quaternion EntityBase::GetAxisAngleQuaternion(int param0) { return q; } -ExprQuaternion EntityBase::GetAxisAngleQuaternionExprs(int param0) { +ExprQuaternion EntityBase::GetAxisAngleQuaternionExprs(int param0) const { ExprQuaternion q; Expr *theta = Expr::From(timesApplied)->Times( @@ -585,43 +682,45 @@ ExprQuaternion EntityBase::GetAxisAngleQuaternionExprs(int param0) { return q; } -Quaternion EntityBase::PointGetQuaternion(void) { +Quaternion EntityBase::PointGetQuaternion() const { Quaternion q; - if(type == POINT_N_ROT_AA) { + if(type == Type::POINT_N_ROT_AA || type == Type::POINT_N_ROT_AXIS_TRANS) { q = GetAxisAngleQuaternion(3); - } else if(type == POINT_N_ROT_TRANS) { + } else if(type == Type::POINT_N_ROT_TRANS) { q = Quaternion::From(param[3], param[4], param[5], param[6]); - } else oops(); + } else ssassert(false, "Unexpected entity type"); return q; } -bool EntityBase::IsFace(void) { +bool EntityBase::IsFace() const { switch(type) { - case FACE_NORMAL_PT: - case FACE_XPROD: - case FACE_N_ROT_TRANS: - case FACE_N_TRANS: - case FACE_N_ROT_AA: + case Type::FACE_NORMAL_PT: + case Type::FACE_XPROD: + case Type::FACE_N_ROT_TRANS: + case Type::FACE_N_TRANS: + case Type::FACE_N_ROT_AA: + case Type::FACE_ROT_NORMAL_PT: + case Type::FACE_N_ROT_AXIS_TRANS: return true; default: return false; } } -ExprVector EntityBase::FaceGetNormalExprs(void) { +ExprVector EntityBase::FaceGetNormalExprs() const { ExprVector r; - if(type == FACE_NORMAL_PT) { + if(type == Type::FACE_NORMAL_PT) { Vector v = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz); r = ExprVector::From(v.WithMagnitude(1)); - } else if(type == FACE_XPROD) { + } else if(type == Type::FACE_XPROD) { ExprVector vc = ExprVector::From(param[0], param[1], param[2]); ExprVector vn = ExprVector::From(numNormal.vx, numNormal.vy, numNormal.vz); r = vc.Cross(vn); r = r.WithMagnitude(Expr::From(1.0)); - } else if(type == FACE_N_ROT_TRANS) { + } else if(type == Type::FACE_N_ROT_TRANS) { // The numerical normal vector gets the rotation; the numerical // normal has magnitude one, and the rotation doesn't change that, // so there's no need to fix it up. @@ -629,46 +728,46 @@ ExprVector EntityBase::FaceGetNormalExprs(void) { ExprQuaternion q = ExprQuaternion::From(param[3], param[4], param[5], param[6]); r = q.Rotate(r); - } else if(type == FACE_N_TRANS) { + } else if(type == Type::FACE_N_TRANS) { r = ExprVector::From(numNormal.vx, numNormal.vy, numNormal.vz); - } else if(type == FACE_N_ROT_AA) { + } else if((type == Type::FACE_N_ROT_AA) || (type == Type::FACE_ROT_NORMAL_PT)) { r = ExprVector::From(numNormal.vx, numNormal.vy, numNormal.vz); ExprQuaternion q = GetAxisAngleQuaternionExprs(3); r = q.Rotate(r); - } else oops(); + } else ssassert(false, "Unexpected entity type"); return r; } -Vector EntityBase::FaceGetNormalNum(void) { +Vector EntityBase::FaceGetNormalNum() const { Vector r; - if(type == FACE_NORMAL_PT) { + if(type == Type::FACE_NORMAL_PT) { r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz); - } else if(type == FACE_XPROD) { + } else if(type == Type::FACE_XPROD) { Vector vc = Vector::From(param[0], param[1], param[2]); Vector vn = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz); r = vc.Cross(vn); - } else if(type == FACE_N_ROT_TRANS) { + } else if(type == Type::FACE_N_ROT_TRANS) { // The numerical normal vector gets the rotation r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz); Quaternion q = Quaternion::From(param[3], param[4], param[5], param[6]); r = q.Rotate(r); - } else if(type == FACE_N_TRANS) { + } else if(type == Type::FACE_N_TRANS) { r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz); - } else if(type == FACE_N_ROT_AA) { + } else if((type == Type::FACE_N_ROT_AA) || (type == Type::FACE_ROT_NORMAL_PT)) { r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz); Quaternion q = GetAxisAngleQuaternion(3); r = q.Rotate(r); - } else oops(); + } else ssassert(false, "Unexpected entity type"); return r.WithMagnitude(1); } -ExprVector EntityBase::FaceGetPointExprs(void) { +ExprVector EntityBase::FaceGetPointExprs() const { ExprVector r; - if(type == FACE_NORMAL_PT) { + if((type == Type::FACE_NORMAL_PT) || (type==Type::FACE_ROT_NORMAL_PT)) { r = SK.GetEntity(point[0])->PointGetExprs(); - } else if(type == FACE_XPROD) { + } else if(type == Type::FACE_XPROD) { r = ExprVector::From(numPoint); - } else if(type == FACE_N_ROT_TRANS) { + } else if(type == Type::FACE_N_ROT_TRANS) { // The numerical point gets the rotation and translation. ExprVector trans = ExprVector::From(param[0], param[1], param[2]); ExprQuaternion q = @@ -676,118 +775,210 @@ ExprVector EntityBase::FaceGetPointExprs(void) { r = ExprVector::From(numPoint); r = q.Rotate(r); r = r.Plus(trans); - } else if(type == FACE_N_TRANS) { + } else if(type == Type::FACE_N_ROT_AXIS_TRANS) { + ExprVector orig = ExprVector::From(numPoint); + ExprVector trans = ExprVector::From(param[0], param[1], param[2]); + ExprVector displace = ExprVector::From(param[4], param[5], param[6]) + .WithMagnitude(Expr::From(param[7])).ScaledBy(Expr::From(timesApplied)); + ExprQuaternion q = GetAxisAngleQuaternionExprs(3); + orig = orig.Minus(trans); + orig = q.Rotate(orig); + r = orig.Plus(trans).Plus(displace); + } else if(type == Type::FACE_N_TRANS) { ExprVector trans = ExprVector::From(param[0], param[1], param[2]); r = ExprVector::From(numPoint); r = r.Plus(trans.ScaledBy(Expr::From(timesApplied))); - } else if(type == FACE_N_ROT_AA) { + } else if(type == Type::FACE_N_ROT_AA) { ExprVector trans = ExprVector::From(param[0], param[1], param[2]); ExprQuaternion q = GetAxisAngleQuaternionExprs(3); r = ExprVector::From(numPoint); r = r.Minus(trans); r = q.Rotate(r); r = r.Plus(trans); - } else oops(); + } else ssassert(false, "Unexpected entity type"); return r; } -Vector EntityBase::FaceGetPointNum(void) { +Vector EntityBase::FaceGetPointNum() const { Vector r; - if(type == FACE_NORMAL_PT) { + if((type == Type::FACE_NORMAL_PT) || (type==Type::FACE_ROT_NORMAL_PT)) { r = SK.GetEntity(point[0])->PointGetNum(); - } else if(type == FACE_XPROD) { + } else if(type == Type::FACE_XPROD) { r = numPoint; - } else if(type == FACE_N_ROT_TRANS) { + } else if(type == Type::FACE_N_ROT_TRANS) { // The numerical point gets the rotation and translation. Vector trans = Vector::From(param[0], param[1], param[2]); Quaternion q = Quaternion::From(param[3], param[4], param[5], param[6]); r = q.Rotate(numPoint); r = r.Plus(trans); - } else if(type == FACE_N_TRANS) { + } else if(type == Type::FACE_N_ROT_AXIS_TRANS) { + Vector offset = Vector::From(param[0], param[1], param[2]); + Vector displace = Vector::From(param[4], param[5], param[6]) + .WithMagnitude(SK.GetParam(param[7])->val).ScaledBy(timesApplied); + Quaternion q = PointGetQuaternion(); + r = numPoint.Minus(offset); + r = q.Rotate(r); + r = r.Plus(offset).Plus(displace); + } else if(type == Type::FACE_N_TRANS) { Vector trans = Vector::From(param[0], param[1], param[2]); r = numPoint.Plus(trans.ScaledBy(timesApplied)); - } else if(type == FACE_N_ROT_AA) { + } else if(type == Type::FACE_N_ROT_AA) { Vector trans = Vector::From(param[0], param[1], param[2]); Quaternion q = GetAxisAngleQuaternion(3); r = numPoint.Minus(trans); r = q.Rotate(r); r = r.Plus(trans); - } else oops(); + } else ssassert(false, "Unexpected entity type"); return r; } -bool EntityBase::HasEndpoints(void) { - return (type == LINE_SEGMENT) || - (type == CUBIC) || - (type == ARC_OF_CIRCLE); +bool EntityBase::HasEndpoints() const { + return (type == Type::LINE_SEGMENT) || + (type == Type::CUBIC) || + (type == Type::ARC_OF_CIRCLE); } -Vector EntityBase::EndpointStart() { - if(type == LINE_SEGMENT) { +Vector EntityBase::EndpointStart() const { + if(type == Type::LINE_SEGMENT) { return SK.GetEntity(point[0])->PointGetNum(); - } else if(type == CUBIC) { + } else if(type == Type::CUBIC) { return CubicGetStartNum(); - } else if(type == ARC_OF_CIRCLE) { + } else if(type == Type::ARC_OF_CIRCLE) { return SK.GetEntity(point[1])->PointGetNum(); - } else { - oops(); - } + } else ssassert(false, "Unexpected entity type"); } -Vector EntityBase::EndpointFinish() { - if(type == LINE_SEGMENT) { +Vector EntityBase::EndpointFinish() const { + if(type == Type::LINE_SEGMENT) { return SK.GetEntity(point[1])->PointGetNum(); - } else if(type == CUBIC) { + } else if(type == Type::CUBIC) { return CubicGetFinishNum(); - } else if(type == ARC_OF_CIRCLE) { + } else if(type == Type::ARC_OF_CIRCLE) { return SK.GetEntity(point[2])->PointGetNum(); - } else { - oops(); + } else ssassert(false, "Unexpected entity type"); +} +static bool PointInPlane(hEntity h, Vector norm, double distance) { + Vector p = SK.GetEntity(h)->PointGetNum(); + return (fabs(norm.Dot(p) - distance) < LENGTH_EPS); +} +bool EntityBase::IsInPlane(Vector norm, double distance) const { + switch(type) { + case Type::LINE_SEGMENT: { + return PointInPlane(point[0], norm, distance) + && PointInPlane(point[1], norm, distance); + } + case Type::CUBIC: + case Type::CUBIC_PERIODIC: { + bool periodic = type == Type::CUBIC_PERIODIC; + int n = periodic ? 3 + extraPoints : extraPoints; + int i; + for (i=0; iNormalN(); + if (!norm.Equals(n) && !norm.Equals(n.Negated())) return false; + return PointInPlane(point[0], norm, distance); + } + + case Type::TTF_TEXT: { + Vector n = Normal()->NormalN(); + if (!norm.Equals(n) && !norm.Equals(n.Negated())) return false; + return PointInPlane(point[0], norm, distance) + && PointInPlane(point[1], norm, distance); + } + + default: + return false; } } -void EntityBase::AddEq(IdList *l, Expr *expr, int index) { +void EntityBase::RectGetPointsExprs(ExprVector *eb, ExprVector *ec) const { + ssassert(type == Type::TTF_TEXT || type == Type::IMAGE, + "Unexpected entity type"); + + EntityBase *a = SK.GetEntity(point[0]); + EntityBase *o = SK.GetEntity(point[1]); + + // Write equations for each point in the current workplane. + // This reduces the complexity of resulting equations. + ExprVector ea = a->PointGetExprsInWorkplane(workplane); + ExprVector eo = o->PointGetExprsInWorkplane(workplane); + + // Take perpendicular vector and scale it by aspect ratio. + ExprVector eu = ea.Minus(eo); + ExprVector ev = ExprVector::From(eu.y, eu.x->Negate(), eu.z).ScaledBy(Expr::From(aspectRatio)); + + *eb = eo.Plus(ev); + *ec = eo.Plus(eu).Plus(ev); +} + +void EntityBase::AddEq(IdList *l, Expr *expr, int index) const { Equation eq; eq.e = expr; eq.h = h.equation(index); l->Add(&eq); } -void EntityBase::GenerateEquations(IdList *l) { +void EntityBase::GenerateEquations(IdList *l) const { switch(type) { - case NORMAL_IN_3D: { + case Type::NORMAL_IN_3D: { ExprQuaternion q = NormalGetExprs(); AddEq(l, (q.Magnitude())->Minus(Expr::From(1)), 0); break; } - case ARC_OF_CIRCLE: { + + case Type::ARC_OF_CIRCLE: { // If this is a copied entity, with its point already fixed // with respect to each other, then we don't want to generate // the distance constraint! - if(SK.GetEntity(point[0])->type != POINT_IN_2D) break; + if(SK.GetEntity(point[0])->type != Type::POINT_IN_2D) break; // If the two endpoints of the arc are constrained coincident // (to make a complete circle), then our distance constraint // would be redundant and therefore overconstrain things. - int i; - for(i = 0; i < SK.constraint.n; i++) { - ConstraintBase *c = &(SK.constraint.elem[i]); - if(c->group.v != group.v) continue; - if(c->type != Constraint::POINTS_COINCIDENT) continue; - - if((c->ptA.v == point[1].v && c->ptB.v == point[2].v) || - (c->ptA.v == point[2].v && c->ptB.v == point[1].v)) - { - break; - } + auto it = std::find_if(SK.constraint.begin(), SK.constraint.end(), + [&](ConstraintBase const &con) { + return (con.group == group) && + (con.type == Constraint::Type::POINTS_COINCIDENT) && + ((con.ptA == point[1] && con.ptB == point[2]) || + (con.ptA == point[2] && con.ptB == point[1])); + }); + if(it != SK.constraint.end()) { + break; } - if(i < SK.constraint.n) break; Expr *ra = Constraint::Distance(workplane, point[0], point[1]); Expr *rb = Constraint::Distance(workplane, point[0], point[2]); AddEq(l, ra->Minus(rb), 0); break; } - default:; - // Most entities do not generate equations. + + case Type::IMAGE: + case Type::TTF_TEXT: { + if(SK.GetEntity(point[0])->type != Type::POINT_IN_2D) break; + EntityBase *b = SK.GetEntity(point[2]); + EntityBase *c = SK.GetEntity(point[3]); + ExprVector eb = b->PointGetExprsInWorkplane(workplane); + ExprVector ec = c->PointGetExprsInWorkplane(workplane); + + ExprVector ebp, ecp; + RectGetPointsExprs(&ebp, &ecp); + + ExprVector beq = eb.Minus(ebp); + AddEq(l, beq.x, 0); + AddEq(l, beq.y, 1); + ExprVector ceq = ec.Minus(ecp); + AddEq(l, ceq.x, 2); + AddEq(l, ceq.y, 3); + break; + } + + default: // Most entities do not generate equations. + break; } } - diff --git a/src/export.cpp b/src/export.cpp index 857ff60..e0c6182 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -7,20 +7,17 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" -#ifndef WIN32 -#include -#endif -#include +#include "config.h" -void SolveSpaceUI::ExportSectionTo(const std::string &filename) { +void SolveSpaceUI::ExportSectionTo(const Platform::Path &filename) { Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); gn = gn.WithMagnitude(1); Group *g = SK.GetGroup(SS.GW.activeGroup); g->GenerateDisplayItems(); if(g->displayMesh.IsEmpty()) { - Error("No solid model present; draw one with extrudes and revolves, " - "or use Export 2d View to export bare lines and curves."); + Error(_("No solid model present; draw one with extrudes and revolves, " + "or use Export 2d View to export bare lines and curves.")); return; } @@ -30,8 +27,8 @@ void SolveSpaceUI::ExportSectionTo(const std::string &filename) { double d; SS.GW.GroupSelection(); -#define gs (SS.GW.gs) - if((gs.n == 0 && g->activeWorkplane.v != Entity::FREE_IN_3D.v)) { + auto const &gs = SS.GW.gs; + if((gs.n == 0 && g->activeWorkplane != Entity::FREE_IN_3D)) { Entity *wrkpl = SK.GetEntity(g->activeWorkplane); origin = wrkpl->WorkplaneGetOffset(); n = wrkpl->Normal()->NormalN(); @@ -61,12 +58,12 @@ void SolveSpaceUI::ExportSectionTo(const std::string &filename) { u = ut.WithMagnitude(1); v = (n.Cross(u)).WithMagnitude(1); } else { - Error("Bad selection for export section. Please select:\n\n" - " * nothing, with an active workplane " - "(workplane is section plane)\n" - " * a face (section plane through face)\n" - " * a point and two line segments " - "(plane through point and parallel to lines)\n"); + Error(_("Bad selection for export section. Please select:\n\n" + " * nothing, with an active workplane " + "(workplane is section plane)\n" + " * a face (section plane through face)\n" + " * a point and two line segments " + "(plane through point and parallel to lines)\n")); return; } SS.GW.ClearSelection(); @@ -81,9 +78,8 @@ void SolveSpaceUI::ExportSectionTo(const std::string &filename) { g->runningMesh.MakeEdgesInPlaneInto(&el, n, d); // If there's a shell, then grab the edges and possibly Beziers. - g->runningShell.MakeSectionEdgesInto(n, d, - &el, - (SS.exportPwlCurves || fabs(SS.exportOffset) > LENGTH_EPS) ? NULL : &bl); + bool export_as_pwl = SS.exportPwlCurves || fabs(SS.exportOffset) > LENGTH_EPS; + g->runningShell.MakeSectionEdgesInto(n, d, &el, export_as_pwl ? NULL : &bl); // All of these are solid model edges, so use the appropriate style. SEdge *se; @@ -95,8 +91,27 @@ void SolveSpaceUI::ExportSectionTo(const std::string &filename) { sb->auxA = Style::SOLID_EDGE; } - el.CullExtraneousEdges(); - bl.CullIdenticalBeziers(); + // Remove all overlapping edges/beziers to merge the areas they describe. + el.CullExtraneousEdges(/*both=*/true); + bl.CullIdenticalBeziers(/*both=*/true); + + // Collect lines and beziers with custom style & export. + for(auto &ent : SK.entity) { + Entity *e = &ent; + if (!e->IsVisible()) continue; + if (e->style.v < Style::FIRST_CUSTOM) continue; + if (!Style::Exportable(e->style.v)) continue; + if (!e->IsInPlane(n,d)) continue; + if (export_as_pwl) { + e->GenerateEdges(&el); + } else { + e->GenerateBezierCurves(&bl); + } + } + + // Only remove half of the overlapping edges/beziers to support TTF Stick Fonts. + el.CullExtraneousEdges(/*both=*/false); + bl.CullIdenticalBeziers(/*both=*/false); // And write the edges. VectorFileWriter *out = VectorFileWriter::ForFile(filename); @@ -110,8 +125,66 @@ void SolveSpaceUI::ExportSectionTo(const std::string &filename) { bl.Clear(); } -void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool wireframe) { - int i; +// This is an awful temporary hack to replace Constraint::GetEdges until we have proper +// export through Canvas. +class GetEdgesCanvas : public Canvas { +public: + Camera camera; + SEdgeList *edges; + + const Camera &GetCamera() const override { + return camera; + } + + void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override { + edges->AddEdge(a, b, Style::CONSTRAINT); + } + void DrawEdges(const SEdgeList &el, hStroke hcs) override { + for(const SEdge &e : el.l) { + edges->AddEdge(e.a, e.b, Style::CONSTRAINT); + } + } + void DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) override { + auto traceEdge = [&](Vector a, Vector b) { edges->AddEdge(a, b, Style::CONSTRAINT); }; + VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera); + } + + void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) override { + // Do nothing + } + + bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { + ssassert(false, "Not implemented"); + } + void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override { + ssassert(false, "Not implemented"); + } + void DrawPoint(const Vector &o, hStroke hcs) override { + ssassert(false, "Not implemented"); + } + void DrawPolygon(const SPolygon &p, hFill hcf) override { + ssassert(false, "Not implemented"); + } + void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {}) override { + ssassert(false, "Not implemented"); + } + void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override { + ssassert(false, "Not implemented"); + } + void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) override { + ssassert(false, "Not implemented"); + } + void InvalidatePixmap(std::shared_ptr pm) override { + ssassert(false, "Not implemented"); + } +}; + +void SolveSpaceUI::ExportViewOrWireframeTo(const Platform::Path &filename, bool exportWireframe) { SEdgeList edges = {}; SBezierList beziers = {}; @@ -119,10 +192,10 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool wir if(!out) return; SS.exportMode = true; - GenerateAll(GENERATE_ALL); + GenerateAll(Generate::ALL); SMesh *sm = NULL; - if(SS.GW.showShaded || SS.GW.showHdnLines) { + if(SS.GW.showShaded || SS.GW.drawOccludedAs != GraphicsWindow::DrawOccludedAs::VISIBLE) { Group *g = SK.GetGroup(SS.GW.activeGroup); g->GenerateDisplayItems(); sm = &(g->displayMesh); @@ -131,8 +204,8 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool wir sm = NULL; } - for(i = 0; i < SK.entity.n; i++) { - Entity *e = &(SK.entity.elem[i]); + for(auto &entity : SK.entity) { + Entity *e = &entity; if(!e->IsVisible()) continue; if(e->construction) continue; @@ -147,28 +220,31 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool wir } } - if(SS.GW.showEdges) { + if(SS.GW.showEdges || SS.GW.showOutlines) { Group *g = SK.GetGroup(SS.GW.activeGroup); g->GenerateDisplayItems(); - SEdgeList *selr = &(g->displayEdges); - SEdge *se; - for(se = selr->l.First(); se; se = selr->l.NextAfter(se)) { - edges.AddEdge(se->a, se->b, Style::SOLID_EDGE); + if(SS.GW.showEdges) { + g->displayOutlines.ListTaggedInto(&edges, Style::SOLID_EDGE); } } if(SS.GW.showConstraints) { if(!out->OutputConstraints(&SK.constraint)) { + GetEdgesCanvas canvas = {}; + canvas.camera = SS.GW.GetCamera(); + canvas.edges = &edges; + // The output format cannot represent constraints directly, // so convert them to edges. - Constraint *c; - for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { - c->GetEdges(&edges); + for(Constraint &c : SK.constraint) { + c.Draw(Constraint::DrawAs::DEFAULT, &canvas); } + + canvas.Clear(); } } - if(wireframe) { + if(exportWireframe) { Vector u = Vector::From(1.0, 0.0, 0.0), v = Vector::From(0.0, 1.0, 0.0), n = Vector::From(0.0, 0.0, 1.0), @@ -205,7 +281,7 @@ void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool wir } SS.justExportedInfo.draw = true; - InvalidateGraphics(); + GW.Invalidate(); } edges.Clear(); @@ -235,24 +311,22 @@ void SolveSpaceUI::ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl, } void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm, - Vector u, Vector v, Vector n, - Vector origin, double cameraTan, - VectorFileWriter *out) + Vector u, Vector v, Vector n, + Vector origin, double cameraTan, + VectorFileWriter *out) { double s = 1.0 / SS.exportScale; // Project into the export plane; so when we're done, z doesn't matter, // and x and y are what goes in the DXF. - SEdge *e; - for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { + for(SEdge *e = sel->l.First(); e; e = sel->l.NextAfter(e)) { // project into the specified csys, and apply export scale (e->a) = e->a.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); (e->b) = e->b.InPerspective(u, v, n, origin, cameraTan).ScaledBy(s); } - SBezier *b; if(sbl) { - for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { + for(SBezier *b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { *b = b->InPerspective(u, v, n, origin, cameraTan); int i; for(i = 0; i <= b->deg; i++) { @@ -331,8 +405,8 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s // Generate the edges where a curved surface turns from front-facing // to back-facing. - if(SS.GW.showEdges) { - root->MakeCertainEdgesInto(sel, SKdNode::TURNING_EDGES, + if(SS.GW.showEdges || SS.GW.showOutlines) { + root->MakeCertainEdgesInto(sel, EdgeKind::TURNING, /*coplanarIsInter=*/false, NULL, NULL, GW.showOutlines ? Style::OUTLINE : Style::SOLID_EDGE); } @@ -352,7 +426,17 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s SEdgeList edges = {}; // Split the original edge against the mesh edges.AddEdge(se->a, se->b, se->auxA); - root->OcclusionTestLine(*se, &edges, cnt, /*removeHidden=*/!SS.GW.showHdnLines); + root->OcclusionTestLine(*se, &edges, cnt); + if(SS.GW.drawOccludedAs == GraphicsWindow::DrawOccludedAs::STIPPLED) { + for(SEdge &se : edges.l) { + if(se.tag == 1) { + se.auxA = Style::HIDDEN_EDGE; + } + } + } else if(SS.GW.drawOccludedAs == GraphicsWindow::DrawOccludedAs::INVISIBLE) { + edges.l.RemoveTagged(); + } + // the occlusion test splits unnecessarily; so fix those edges.MergeCollinearSegments(se->a, se->b); cnt++; @@ -371,7 +455,7 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s // segments with zero-length projections. sel->l.ClearTags(); for(int i = 0; i < sel->l.n; ++i) { - SEdge *sei = &sel->l.elem[i]; + SEdge *sei = &sel->l[i]; hStyle hsi = { (uint32_t)sei->auxA }; Style *si = Style::Get(hsi); if(sei->tag != 0) continue; @@ -388,7 +472,7 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s } for(int j = i + 1; j < sel->l.n; ++j) { - SEdge *sej = &sel->l.elem[j]; + SEdge *sej = &sel->l[j]; if(sej->tag != 0) continue; Vector *pAj = &sej->a; @@ -490,12 +574,13 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s // We kept the line segments and Beziers separate until now; but put them // all together, and also project everything into the xy plane, since not // all export targets ignore the z component of the points. - for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { + ssassert(sbl != nullptr, "Adding line segments to beziers assumes bezier list is non-null."); + for(SEdge *e = sel->l.First(); e; e = sel->l.NextAfter(e)) { SBezier sb = SBezier::From(e->a, e->b); sb.auxA = e->auxA; sbl->l.Add(&sb); } - for(b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { + for(SBezier *b = sbl->l.First(); b; b = sbl->l.NextAfter(b)) { for(int i = 0; i <= b->deg; i++) { b->ctrl[i].z = 0; } @@ -504,7 +589,7 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s // If possible, then we will assemble these output curves into loops. They // will then get exported as closed paths. SBezierLoopSetSet sblss = {}; - SBezierList leftovers = {}; + SBezierLoopSet leftovers = {}; SSurface srf = SSurface::FromPlane(Vector::From(0, 0, 0), Vector::From(1, 0, 0), Vector::From(0, 1, 0)); @@ -517,14 +602,11 @@ void SolveSpaceUI::ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *s &allClosed, ¬ClosedAt, NULL, NULL, &leftovers); - for(b = leftovers.l.First(); b; b = leftovers.l.NextAfter(b)) { - sblss.AddOpenPath(b); - } + sblss.l.Add(&leftovers); // Now write the lines and triangles to the output file out->OutputLinesAndMesh(&sblss, &sms); - leftovers.Clear(); spxyz.Clear(); sblss.Clear(); smp.Clear(); @@ -537,45 +619,45 @@ double VectorFileWriter::MmToPts(double mm) { return (mm/25.4)*72; } -VectorFileWriter *VectorFileWriter::ForFile(const std::string &filename) { +VectorFileWriter *VectorFileWriter::ForFile(const Platform::Path &filename) { VectorFileWriter *ret; bool needOpen = true; - if(FilenameHasExtension(filename, ".dxf")) { + if(filename.HasExtension("dxf")) { static DxfFileWriter DxfWriter; ret = &DxfWriter; needOpen = false; - } else if(FilenameHasExtension(filename, ".ps") || FilenameHasExtension(filename, ".eps")) { + } else if(filename.HasExtension("ps") || filename.HasExtension("eps")) { static EpsFileWriter EpsWriter; ret = &EpsWriter; - } else if(FilenameHasExtension(filename, ".pdf")) { + } else if(filename.HasExtension("pdf")) { static PdfFileWriter PdfWriter; ret = &PdfWriter; - } else if(FilenameHasExtension(filename, ".svg")) { + } else if(filename.HasExtension("svg")) { static SvgFileWriter SvgWriter; ret = &SvgWriter; - } else if(FilenameHasExtension(filename, ".plt")||FilenameHasExtension(filename, ".hpgl")) { + } else if(filename.HasExtension("plt") || filename.HasExtension("hpgl")) { static HpglFileWriter HpglWriter; ret = &HpglWriter; - } else if(FilenameHasExtension(filename, ".step")||FilenameHasExtension(filename, ".stp")) { + } else if(filename.HasExtension("step") || filename.HasExtension("stp")) { static Step2dFileWriter Step2dWriter; ret = &Step2dWriter; - } else if(FilenameHasExtension(filename, ".txt")) { + } else if(filename.HasExtension("txt") || filename.HasExtension("ngc")) { static GCodeFileWriter GCodeWriter; ret = &GCodeWriter; } else { Error("Can't identify output file type from file extension of " "filename '%s'; try " - ".step, .stp, .dxf, .svg, .plt, .hpgl, .pdf, .txt, " + ".step, .stp, .dxf, .svg, .plt, .hpgl, .pdf, .txt, .ngc, " ".eps, or .ps.", - filename.c_str()); + filename.raw.c_str()); return NULL; } ret->filename = filename; if(!needOpen) return ret; - FILE *f = ssfopen(filename, "wb"); + FILE *f = OpenFile(filename, "wb"); if(!f) { - Error("Couldn't write to '%s'", filename.c_str()); + Error("Couldn't write to '%s'", filename.raw.c_str()); return NULL; } ret->f = f; @@ -635,13 +717,16 @@ void VectorFileWriter::OutputLinesAndMesh(SBezierLoopSetSet *sblss, SMesh *sm) { ptMin.y -= s*SS.exportMargin.bottom; ptMax.y += s*SS.exportMargin.top; } else { - ptMin.x = -(s*SS.exportCanvas.dx); - ptMin.y = -(s*SS.exportCanvas.dy); + ptMin.x = (s*SS.exportCanvas.dx); + ptMin.y = (s*SS.exportCanvas.dy); ptMax.x = ptMin.x + (s*SS.exportCanvas.width); ptMax.y = ptMin.y + (s*SS.exportCanvas.height); } StartFile(); + if(SS.exportBackgroundColor) { + Background(SS.backgroundColor); + } if(sm && SS.exportShadedTriangles) { for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { Triangle(tr); @@ -659,8 +744,8 @@ void VectorFileWriter::OutputLinesAndMesh(SBezierLoopSetSet *sblss, SMesh *sm) { hStyle hs = { (uint32_t)b->auxA }; Style *stl = Style::Get(hs); double lineWidth = Style::WidthMm(b->auxA)*s; - RgbaColor strokeRgb = Style::Color(hs, true); - RgbaColor fillRgb = Style::FillColor(hs, true); + RgbaColor strokeRgb = Style::Color(hs, /*forExport=*/true); + RgbaColor fillRgb = Style::FillColor(hs, /*forExport=*/true); StartPath(strokeRgb, lineWidth, stl->filled, fillRgb, hs); for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { @@ -677,9 +762,9 @@ void VectorFileWriter::OutputLinesAndMesh(SBezierLoopSetSet *sblss, SMesh *sm) { void VectorFileWriter::BezierAsPwl(SBezier *sb) { List lv = {}; sb->MakePwlInto(&lv, SS.ExportChordTolMm()); - int i; - for(i = 1; i < lv.n; i++) { - SBezier sb = SBezier::From(lv.elem[i-1], lv.elem[i]); + + for(int i = 1; i < lv.n; i++) { + SBezier sb = SBezier::From(lv[i-1], lv[i]); Bezier(&sb); } lv.Clear(); @@ -725,43 +810,57 @@ void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) { //----------------------------------------------------------------------------- // Export a triangle mesh, in the requested format. //----------------------------------------------------------------------------- -void SolveSpaceUI::ExportMeshTo(const std::string &filename) { +void SolveSpaceUI::ExportMeshTo(const Platform::Path &filename) { SS.exportMode = true; - GenerateAll(GENERATE_ALL); + GenerateAll(Generate::ALL); Group *g = SK.GetGroup(SS.GW.activeGroup); g->GenerateDisplayItems(); SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); if(m->IsEmpty()) { - Error("Active group mesh is empty; nothing to export."); + Error(_("Active group mesh is empty; nothing to export.")); return; } - FILE *f = ssfopen(filename, "wb"); + FILE *f = OpenFile(filename, "wb"); if(!f) { - Error("Couldn't write to '%s'", filename.c_str()); + Error("Couldn't write to '%s'", filename.raw.c_str()); return; } - - if(FilenameHasExtension(filename, ".stl")) { + ShowNakedEdges(/*reportOnlyWhenNotOkay=*/true); + if(filename.HasExtension("stl")) { ExportMeshAsStlTo(f, m); - } else if(FilenameHasExtension(filename, ".obj")) { - ExportMeshAsObjTo(f, m); - } else if(FilenameHasExtension(filename, ".js") || - FilenameHasExtension(filename, ".html")) { - SEdgeList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayEdges); + } else if(filename.HasExtension("obj")) { + Platform::Path mtlFilename = filename.WithExtension("mtl"); + FILE *fMtl = OpenFile(mtlFilename, "wb"); + if(!fMtl) { + Error("Couldn't write to '%s'", filename.raw.c_str()); + return; + } + + fprintf(f, "mtllib %s\n", mtlFilename.FileName().c_str()); + ExportMeshAsObjTo(f, fMtl, m); + + fclose(fMtl); + } else if(filename.HasExtension("q3do")) { + ExportMeshAsQ3doTo(f, m); + } else if(filename.HasExtension("js") || + filename.HasExtension("html")) { + SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines); ExportMeshAsThreeJsTo(f, filename, m, e); + } else if(filename.HasExtension("wrl")) { + ExportMeshAsVrmlTo(f, filename, m); } else { Error("Can't identify output file type from file extension of " - "filename '%s'; try .stl, .obj, .js.", filename.c_str()); + "filename '%s'; try .stl, .obj, .js, .html.", filename.raw.c_str()); } fclose(f); SS.justExportedInfo.showOrigin = false; SS.justExportedInfo.draw = true; - InvalidateGraphics(); + GW.Invalidate(); } //----------------------------------------------------------------------------- @@ -779,7 +878,7 @@ void SolveSpaceUI::ExportMeshAsStlTo(FILE *f, SMesh *sm) { double s = SS.exportScale; int i; for(i = 0; i < sm->l.n; i++) { - STriangle *tr = &(sm->l.elem[i]); + STriangle *tr = &(sm->l[i]); Vector n = tr->Normal().WithMagnitude(1); float w; w = (float)n.x; fwrite(&w, 4, 1, f); @@ -799,547 +898,137 @@ void SolveSpaceUI::ExportMeshAsStlTo(FILE *f, SMesh *sm) { } } +//----------------------------------------------------------------------------- +// Export the mesh as a Q3DO (https://github.com/q3k/q3d) file. +//----------------------------------------------------------------------------- + +#include "q3d_object_generated.h" +void SolveSpaceUI::ExportMeshAsQ3doTo(FILE *f, SMesh *sm) { + flatbuffers::FlatBufferBuilder builder(1024); + double s = SS.exportScale; + + // Create a material for every colour used, keep note of triangles belonging to color/material. + std::map, RgbaColorCompare> materials; + std::map>, RgbaColorCompare> materialTriangles; + for (const STriangle &t : sm->l) { + auto color = t.meta.color; + if (materials.find(color) == materials.end()) { + auto name = builder.CreateString(ssprintf("Color #%02x%02x%02x%02x", color.red, color.green, color.blue, color.alpha)); + auto co = q3d::CreateColor(builder, color.red, color.green, color.blue, color.alpha); + auto mo = q3d::CreateMaterial(builder, name, co); + materials.emplace(color, mo); + } + + Vector faceNormal = t.Normal(); + auto a = q3d::Vector3((float)(t.a.x/s), (float)(t.a.y/s), (float)(t.a.z/s)); + auto b = q3d::Vector3((float)(t.b.x/s), (float)(t.b.y/s), (float)(t.b.z/s)); + auto c = q3d::Vector3((float)(t.c.x/s), (float)(t.c.y/s), (float)(t.c.z/s)); + auto fn = q3d::Vector3((float)faceNormal.x, (float)faceNormal.y, (float)faceNormal.x); + auto n1 = q3d::Vector3((float)t.normals[0].x, (float)t.normals[0].y, (float)t.normals[0].z); + auto n2 = q3d::Vector3((float)t.normals[1].x, (float)t.normals[1].y, (float)t.normals[1].z); + auto n3 = q3d::Vector3((float)t.normals[2].x, (float)t.normals[2].y, (float)t.normals[2].z); + auto tri = q3d::CreateTriangle(builder, &a, &b, &c, &fn, &n1, &n2, &n3); + materialTriangles[color].push_back(tri); + } + + // Build all meshes sorted by material. + std::vector> meshes; + for (auto &it : materials) { + auto &mato = it.second; + auto to = builder.CreateVector(materialTriangles[it.first]); + auto mo = q3d::CreateMesh(builder, to, mato); + meshes.push_back(mo); + } + + auto mo = builder.CreateVector(meshes); + auto o = q3d::CreateObject(builder, mo); + q3d::FinishObjectBuffer(builder, o); + fwrite(builder.GetBufferPointer(), builder.GetSize(), 1, f); +} + //----------------------------------------------------------------------------- // Export the mesh as Wavefront OBJ format. This requires us to reduce all the // identical vertices to the same identifier, so do that first. //----------------------------------------------------------------------------- -void SolveSpaceUI::ExportMeshAsObjTo(FILE *f, SMesh *sm) { - SPointList spl = {}; - STriangle *tr; - for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { - spl.IncrementTagFor(tr->a); - spl.IncrementTagFor(tr->b); - spl.IncrementTagFor(tr->c); +void SolveSpaceUI::ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm) { + std::map colors; + for(const STriangle &t : sm->l) { + RgbaColor color = t.meta.color; + if(colors.find(color) == colors.end()) { + std::string id = ssprintf("h%02x%02x%02x", + color.red, + color.green, + color.blue); + colors.emplace(color, id); + } + for(int i = 0; i < 3; i++) { + fprintf(fObj, "v %.10f %.10f %.10f\n", + CO(t.vertices[i].ScaledBy(1 / SS.exportScale))); + } } - // Output all the vertices. - SPoint *sp; - for(sp = spl.l.First(); sp; sp = spl.l.NextAfter(sp)) { - fprintf(f, "v %.10f %.10f %.10f\r\n", - sp->p.x / SS.exportScale, - sp->p.y / SS.exportScale, - sp->p.z / SS.exportScale); + for(auto &it : colors) { + fprintf(fMtl, "newmtl %s\n", + it.second.c_str()); + fprintf(fMtl, "Kd %.3f %.3f %.3f\n", + it.first.redF(), it.first.greenF(), it.first.blueF()); } - // And now all the triangular faces, in terms of those vertices. The - // file format counts from 1, not 0. - for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { - fprintf(f, "f %d %d %d\r\n", - spl.IndexForPoint(tr->a) + 1, - spl.IndexForPoint(tr->b) + 1, - spl.IndexForPoint(tr->c) + 1); + for(const STriangle &t : sm->l) { + for(int i = 0; i < 3; i++) { + Vector n = t.normals[i].WithMagnitude(1.0); + fprintf(fObj, "vn %.10f %.10f %.10f\n", + CO(n)); + } } - spl.Clear(); + RgbaColor currentColor = {}; + for(int i = 0; i < sm->l.n; i++) { + const STriangle &t = sm->l[i]; + if(!currentColor.Equals(t.meta.color)) { + currentColor = t.meta.color; + fprintf(fObj, "usemtl %s\n", colors[currentColor].c_str()); + } + + fprintf(fObj, "f %d//%d %d//%d %d//%d\n", + i * 3 + 1, i * 3 + 1, + i * 3 + 2, i * 3 + 2, + i * 3 + 3, i * 3 + 3); + } } //----------------------------------------------------------------------------- // Export the mesh as a JavaScript script, which is compatible with Three.js. //----------------------------------------------------------------------------- -void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, - SMesh *sm, SEdgeList *sel) +void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename, + SMesh *sm, SOutlineList *sol) { SPointList spl = {}; STriangle *tr; - SEdge *e; Vector bndl, bndh; - const char htmlbegin0[] = R"( + const char htmlbegin[] = R"( Three.js Solvespace Mesh - - + + + - @@ -1366,33 +1055,25 @@ SolvespaceCamera = function(renderWidth, renderHeight, scale, up, right, offset) double largerBoundXY = max((bndh.x - bndl.x), (bndh.y - bndl.y)); double largerBoundZ = max(largerBoundXY, (bndh.z - bndl.z + 1)); - std::string extension = filename, - noExtFilename = filename; - size_t dot = noExtFilename.rfind('.'); - extension.erase(0, dot + 1); - noExtFilename.erase(dot); - - std::string baseFilename = noExtFilename; - size_t lastSlash = baseFilename.rfind(PATH_SEP); - if(lastSlash == std::string::npos) oops(); - baseFilename.erase(0, lastSlash + 1); - - for(size_t i = 0; i < baseFilename.length(); i++) { - if(!isalpha(baseFilename[i]) && - /* also permit UTF-8 */ !((unsigned char)baseFilename[i] >= 0x80)) - baseFilename[i] = '_'; + std::string basename = filename.FileStem(); + for(size_t i = 0; i < basename.length(); i++) { + if(!(isalnum(basename[i]) || ((unsigned)basename[i] >= 0x80))) { + basename[i] = '_'; + } } - if(extension == "html") { - fputs(htmlbegin0, f); - fputs(htmlbegin1, f); + if(filename.HasExtension("html")) { + fprintf(f, htmlbegin, + LoadStringFromGzip("threejs/three-r76.js.gz").c_str(), + LoadStringFromGzip("threejs/hammer-2.0.8.js.gz").c_str(), + LoadString("threejs/SolveSpaceControls.js").c_str()); } fprintf(f, "var solvespace_model_%s = {\n" " bounds: {\n" " x: %f, y: %f, near: %f, far: %f, z: %f, edgeBias: %f\n" " },\n", - baseFilename.c_str(), + basename.c_str(), largerBoundXY, largerBoundXY, 1.0, @@ -1406,8 +1087,7 @@ SolvespaceCamera = function(renderWidth, renderHeight, scale, up, right, offset) // Directional. int lightCount; - for(lightCount = 0; lightCount < 2; lightCount++) - { + for(lightCount = 0; lightCount < 2; lightCount++) { fprintf(f, " {\n" " intensity: %f, direction: [%f, %f, %f]\n" " },\n", @@ -1465,86 +1145,170 @@ SolvespaceCamera = function(renderWidth, renderHeight, scale, up, right, offset) fputs(" ],\n" " edges: [\n", f); // Output edges. Assume user's model colors do not obscure white edges. - for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { + for(const SOutline &so : sol->l) { + if(so.tag == 0) continue; fprintf(f, " [[%f, %f, %f], [%f, %f, %f]],\n", - e->a.x / SS.exportScale, - e->a.y / SS.exportScale, - e->a.z / SS.exportScale, - e->b.x / SS.exportScale, - e->b.y / SS.exportScale, - e->b.z / SS.exportScale); + so.a.x / SS.exportScale, + so.a.y / SS.exportScale, + so.a.z / SS.exportScale, + so.b.x / SS.exportScale, + so.b.y / SS.exportScale, + so.b.z / SS.exportScale); } fputs(" ]\n};\n", f); - if(extension == "html") - fprintf(f, htmlend, baseFilename.c_str()); + if(filename.HasExtension("html")) { + fprintf(f, htmlend, + basename.c_str(), + SS.GW.scale, + CO(SS.GW.offset), + CO(SS.GW.projUp), + CO(SS.GW.projRight)); + } spl.Clear(); } //----------------------------------------------------------------------------- -// Export a view of the model as an image; we just take a screenshot, by -// rendering the view in the usual way and then copying the pixels. +// Export the mesh as a VRML text file / WRL. //----------------------------------------------------------------------------- -void SolveSpaceUI::ExportAsPngTo(const std::string &filename) { - int w = (int)SS.GW.width, h = (int)SS.GW.height; - // No guarantee that the back buffer contains anything valid right now, - // so repaint the scene. And hide the toolbar too. - bool prevShowToolbar = SS.showToolbar; - SS.showToolbar = false; -#ifndef WIN32 - std::unique_ptr gloffscreen(new GLOffscreen); - gloffscreen->begin(w, h); -#endif - SS.GW.Paint(); - SS.showToolbar = prevShowToolbar; - - FILE *f = ssfopen(filename, "wb"); - if(!f) goto err; - - png_struct *png_ptr; png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, - NULL, NULL, NULL); - if(!png_ptr) goto err; - - png_info *info_ptr; info_ptr = png_create_info_struct(png_ptr); - if(!png_ptr) goto err; - - if(setjmp(png_jmpbuf(png_ptr))) goto err; - - png_init_io(png_ptr, f); - - // glReadPixels wants to align things on 4-boundaries, and there's 3 - // bytes per pixel. As long as the row width is divisible by 4, all - // works out. - w &= ~3; h &= ~3; - - png_set_IHDR(png_ptr, info_ptr, w, h, - 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT); - - png_write_info(png_ptr, info_ptr); - - // Get the pixel data from the framebuffer - uint8_t *pixels; pixels = (uint8_t *)AllocTemporary(3*w*h); - uint8_t **rowptrs; rowptrs = (uint8_t **)AllocTemporary(h*sizeof(uint8_t *)); - glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pixels); - - int y; - for(y = 0; y < h; y++) { - // gl puts the origin at lower left, but png puts it top left - rowptrs[y] = pixels + ((h - 1) - y)*(3*w); - } - png_write_image(png_ptr, rowptrs); - - png_write_end(png_ptr, info_ptr); - png_destroy_write_struct(&png_ptr, &info_ptr); - fclose(f); - return; +void SolveSpaceUI::ExportMeshAsVrmlTo(FILE *f, const Platform::Path &filename, SMesh *sm) { + struct STriangleSpan { + STriangle *first, *past_last; + + STriangle *begin() const { return first; } + STriangle *end() const { return past_last; } + }; + + + std::string basename = filename.FileStem(); + for(auto & c : basename) { + if(!(isalnum(c) || ((unsigned)c >= 0x80))) { + c = '_'; + } + } + + fprintf(f, "#VRML V2.0 utf8\n" + "#Exported from SolveSpace %s\n" + "\n" + "DEF %s Transform {\n" + " children [", + PACKAGE_VERSION, + basename.c_str()); + + + std::map> opacities; + STriangle *start = sm->l.begin(); + std::uint8_t last_opacity = start->meta.color.alpha; + for(auto & tr : sm->l) { + if(tr.meta.color.alpha != last_opacity) { + opacities[last_opacity].push_back(STriangleSpan{start, &tr}); + start = &tr; + last_opacity = start->meta.color.alpha; + } + } + opacities[last_opacity].push_back(STriangleSpan{start, sm->l.end()}); + + for(auto && op : opacities) { + fprintf(f, "\n" + " Shape {\n" + " appearance Appearance {\n" + " material DEF %s_material_%u Material {\n" + " diffuseColor %f %f %f\n" + " ambientIntensity %f\n" + " transparency %f\n" + " }\n" + " }\n" + " geometry IndexedFaceSet {\n" + " colorPerVertex TRUE\n" + " coord Coordinate { point [\n", + basename.c_str(), + (unsigned)op.first, + SS.ambientIntensity, + SS.ambientIntensity, + SS.ambientIntensity, + SS.ambientIntensity, + 1.f - ((float)op.first / 255.0f)); + + SPointList spl = {}; + + for(const auto & sp : op.second) { + for(const auto & tr : sp) { + spl.IncrementTagFor(tr.a); + spl.IncrementTagFor(tr.b); + spl.IncrementTagFor(tr.c); + } + } -err: - Error("Error writing PNG file '%s'", filename.c_str()); - if(f) fclose(f); - return; + // Output all the vertices. + for(auto sp : spl.l) { + fprintf(f, " %f %f %f,\n", + sp.p.x / SS.exportScale, + sp.p.y / SS.exportScale, + sp.p.z / SS.exportScale); + } + + fputs(" ] }\n" + " coordIndex [\n", f); + // And now all the triangular faces, in terms of those vertices. + for(const auto & sp : op.second) { + for(const auto & tr : sp) { + fprintf(f, " %d, %d, %d, -1,\n", + spl.IndexForPoint(tr.a), + spl.IndexForPoint(tr.b), + spl.IndexForPoint(tr.c)); + } + } + + fputs(" ]\n" + " color Color { color [\n", f); + // Output triangle colors. + std::vector triangle_colour_ids; + std::vector colours_present; + for(const auto & sp : op.second) { + for(const auto & tr : sp) { + const auto colour_itr = std::find_if(colours_present.begin(), colours_present.end(), + [&](const RgbaColor & c) { + return c.Equals(tr.meta.color); + }); + if(colour_itr == colours_present.end()) { + fprintf(f, " %.10f %.10f %.10f,\n", + tr.meta.color.redF(), + tr.meta.color.greenF(), + tr.meta.color.blueF()); + triangle_colour_ids.push_back(colours_present.size()); + colours_present.insert(colours_present.end(), tr.meta.color); + } else { + triangle_colour_ids.push_back(colour_itr - colours_present.begin()); + } + } + } + + fputs(" ] }\n" + " colorIndex [\n", f); + + for(auto colour_idx : triangle_colour_ids) { + fprintf(f, " %d, %d, %d, -1,\n", colour_idx, colour_idx, colour_idx); + } + + fputs(" ]\n" + " }\n" + " }\n", f); + + spl.Clear(); + } + + fputs(" ]\n" + "}\n", f); } +//----------------------------------------------------------------------------- +// Export a view of the model as an image; we just take a screenshot, by +// rendering the view in the usual way and then copying the pixels. +//----------------------------------------------------------------------------- +void SolveSpaceUI::ExportAsPngTo(const Platform::Path &filename) { + screenshotFile = filename; + // The rest of the work is done in the next redraw. + GW.Invalidate(); +} diff --git a/src/exportstep.cpp b/src/exportstep.cpp index 8e3936c..db42dc0 100644 --- a/src/exportstep.cpp +++ b/src/exportstep.cpp @@ -5,7 +5,7 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -void StepFileWriter::WriteHeader(void) { +void StepFileWriter::WriteHeader() { fprintf(f, "ISO-10303-21;\n" "HEADER;\n" @@ -66,7 +66,7 @@ void StepFileWriter::WriteHeader(void) { // Start the ID somewhere beyond the header IDs. id = 200; } -void StepFileWriter::WriteProductHeader(void) { +void StepFileWriter::WriteProductHeader() { fprintf(f, "#175 = SHAPE_DEFINITION_REPRESENTATION(#176, #169);\n" "#176 = PRODUCT_DEFINITION_SHAPE('Version', 'Test Part', #177);\n" @@ -117,11 +117,11 @@ int StepFileWriter::ExportCurve(SBezier *sb) { } int StepFileWriter::ExportCurveLoop(SBezierLoop *loop, bool inner) { - if(loop->l.n < 1) oops(); + ssassert(loop->l.n >= 1, "Expected at least one loop"); List listOfTrims = {}; - SBezier *sb = &(loop->l.elem[loop->l.n - 1]); + SBezier *sb = loop->l.Last(); // Generate "exactly closed" contours, with the same vertex id for the // finish of a previous edge and the start of the next one. So we need @@ -251,14 +251,14 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) { List listOfLoops = {}; // Create the face outer boundary from the outer loop. - int fob = ExportCurveLoop(loop, false); + int fob = ExportCurveLoop(loop, /*inner=*/false); listOfLoops.Add(&fob); // And create the face inner boundaries from any inner loops that // lie within this contour. loop = sbls->l.NextAfter(loop); for(; loop; loop = sbls->l.NextAfter(loop)) { - int fib = ExportCurveLoop(loop, true); + int fib = ExportCurveLoop(loop, /*inner=*/true); listOfLoops.Add(&fib); } @@ -273,9 +273,45 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) { } fprintf(f, "),#%d,.T.);\n", srfid); - fprintf(f, "\n"); advancedFaces.Add(&advFaceId); + // Export the surface color and transparency + // https://www.cax-if.org/documents/rec_prac_styling_org_v16.pdf sections 4.4.2 4.2.4 etc. + // https://tracker.dev.opencascade.org/view.php?id=31550 + fprintf(f, "#%d=COLOUR_RGB('',%.2f,%.2f,%.2f);\n", ++id, ss->color.redF(), + ss->color.greenF(), ss->color.blueF()); + +/* // This works in Kisters 3DViewStation but not in KiCAD and Horison EDA, + // it seems they do not support transparency so use the more verbose one below + fprintf(f, "#%d=SURFACE_STYLE_TRANSPARENT(%.2f);\n", ++id, 1.0 - ss->color.alphaF()); + ++id; + fprintf(f, "#%d=SURFACE_STYLE_RENDERING_WITH_PROPERTIES(.NORMAL_SHADING.,#%d,(#%d));\n", + id, id - 2, id - 1); + ++id; + fprintf(f, "#%d=SURFACE_SIDE_STYLE('',(#%d));\n", id, id - 1); +*/ + + // This works in Horison EDA but is more verbose. + ++id; + fprintf(f, "#%d=FILL_AREA_STYLE_COLOUR('',#%d);\n", id, id - 1); + ++id; + fprintf(f, "#%d=FILL_AREA_STYLE('',(#%d));\n", id, id - 1); + ++id; + fprintf(f, "#%d=SURFACE_STYLE_FILL_AREA(#%d);\n", id, id - 1); + fprintf(f, "#%d=SURFACE_STYLE_TRANSPARENT(%.2f);\n", ++id, 1.0 - ss->color.alphaF()); + ++id; + fprintf(f, "#%d=SURFACE_STYLE_RENDERING_WITH_PROPERTIES(.NORMAL_SHADING.,#%d,(#%d));\n", id, id - 5, id - 1); + ++id; + fprintf(f, "#%d=SURFACE_SIDE_STYLE('',(#%d, #%d));\n", id, id - 3, id - 1); + + ++id; + fprintf(f, "#%d=SURFACE_STYLE_USAGE(.BOTH.,#%d);\n", id, id - 1); + ++id; + fprintf(f, "#%d=PRESENTATION_STYLE_ASSIGNMENT((#%d));\n", id, id - 1); + ++id; + fprintf(f, "#%d=STYLED_ITEM('',(#%d),#%d);\n", id, id - 1, advFaceId); + fprintf(f, "\n"); + id++; listOfLoops.Clear(); } @@ -283,7 +319,7 @@ void StepFileWriter::ExportSurface(SSurface *ss, SBezierList *sbl) { spxyz.Clear(); } -void StepFileWriter::WriteFooter(void) { +void StepFileWriter::WriteFooter() { fprintf(f, "\n" "ENDSEC;\n" @@ -292,22 +328,23 @@ void StepFileWriter::WriteFooter(void) { ); } -void StepFileWriter::ExportSurfacesTo(const std::string &filename) { +void StepFileWriter::ExportSurfacesTo(const Platform::Path &filename) { Group *g = SK.GetGroup(SS.GW.activeGroup); SShell *shell = &(g->runningShell); - if(shell->surface.n == 0) { + if(shell->surface.IsEmpty()) { Error("The model does not contain any surfaces to export.%s", - g->runningMesh.l.n > 0 ? - "\n\nThe model does contain triangles from a mesh, but " - "a triangle mesh cannot be exported as a STEP file. Try " - "File -> Export Mesh... instead." : ""); + !g->runningMesh.l.IsEmpty() + ? "\n\nThe model does contain triangles from a mesh, but " + "a triangle mesh cannot be exported as a STEP file. Try " + "File -> Export Mesh... instead." + : ""); return; } - f = ssfopen(filename, "wb"); + f = OpenFile(filename, "wb"); if(!f) { - Error("Couldn't write to '%s'", filename.c_str()); + Error("Couldn't write to '%s'", filename.raw.c_str()); return; } @@ -318,7 +355,8 @@ void StepFileWriter::ExportSurfacesTo(const std::string &filename) { SSurface *ss; for(ss = shell->surface.First(); ss; ss = shell->surface.NextAfter(ss)) { - if(ss->trim.n == 0) continue; + if(ss->trim.IsEmpty()) + continue; // Get all of the loops of Beziers that trim our surface (with each // Bezier split so that we use the section as t goes from 0 to 1), and @@ -354,7 +392,7 @@ void StepFileWriter::ExportSurfacesTo(const std::string &filename) { advancedFaces.Clear(); } -void StepFileWriter::WriteWireframe(void) { +void StepFileWriter::WriteWireframe() { fprintf(f, "#%d=GEOMETRIC_CURVE_SET('curves',(", id); int *c; for(c = curves.First(); c; c = curves.NextAfter(c)) { diff --git a/src/exportvector.cpp b/src/exportvector.cpp index ca625ec..4a184ab 100644 --- a/src/exportvector.cpp +++ b/src/exportvector.cpp @@ -6,147 +6,15 @@ #include #include "solvespace.h" -VectorFileWriter::~VectorFileWriter() { - // This out-of-line virtual method definition quells the following warning - // from Clang++: "'VectorFileWriter' has no out-of-line virtual method - // definitions; its vtable will be emitted in every translation unit - // [-Wweak-vtables]" -} - -class PolylineBuilder { -public: - struct Edge; - - struct Vertex { - Vector pos; - std::vector edges; - - bool getNext(hStyle hs, Vertex **next) { - auto it = std::find_if(edges.begin(), edges.end(), [&](const Edge *e) { - return e->tag == 0 && e->style.v == hs.v; - }); - if(it != edges.end()) { - (*it)->tag = 1; - *next = (*it)->getOtherVertex(this); - return true; - } else { - return false; - } - } - - size_t countEdgesWithTagAndStyle(int tag, hStyle hs) const { - return std::count_if(edges.begin(), edges.end(), [&](const Edge *e) { - return e->tag == tag && e->style.v == hs.v; - }); - } - }; - - struct Edge { - Vertex *a; - Vertex *b; - hStyle style; - int tag; - - Vertex *getOtherVertex(Vertex *v) { - if(a == v) return b; - if(b == v) return a; - return NULL; - } - - bool getStartAndNext(Vertex **start, Vertex **next, bool loop) { - size_t numA = a->countEdgesWithTagAndStyle(0, style); - size_t numB = b->countEdgesWithTagAndStyle(0, style); - - if((numA == 1 && numB > 1) || (loop && numA > 1 && numB > 1)) { - *start = a; - *next = b; - return true; - } - - if(numA > 1 && numB == 1) { - *start = b; - *next = a; - return true; - } - - return false; - } - }; - - struct VectorHash { - size_t operator()(const Vector &v) const { - static const size_t size = std::numeric_limits::max() / 2 - 1; - static const double eps = (4.0 * LENGTH_EPS); - - double x = fabs(v.x) / eps; - double y = fabs(v.y) / eps; - - size_t xs = size_t(fmod(x, double(size))); - size_t ys = size_t(fmod(y, double(size))); - - return ys * size + xs; - } - }; - - struct VectorPred { - bool operator()(Vector a, Vector b) const { - return a.Equals(b, LENGTH_EPS); - } - }; - - std::unordered_map vertices; - std::vector edges; - - ~PolylineBuilder() { - clear(); - } - - void clear() { - for(Edge *e : edges) { - delete e; - } - edges.clear(); - - for(auto &v : vertices) { - delete v.second; - } - vertices.clear(); - } - - Vertex *addVertex(const Vector &pos) { - auto it = vertices.find(pos); - if(it != vertices.end()) { - return it->second; - } - - Vertex *result = new Vertex; - result->pos = pos; - vertices.emplace(pos, result); - - return result; - } - - Edge *addEdge(const Vector &p0, const Vector &p1, int style) { - Vertex *v0 = addVertex(p0); - Vertex *v1 = addVertex(p1); - if(v0 == v1) return NULL; - - Edge *edge = new Edge { v0, v1, hStyle { (uint32_t)style }, 0 }; - edges.push_back(edge); - - v0->edges.push_back(edge); - v1->edges.push_back(edge); - - return edge; - } -}; - //----------------------------------------------------------------------------- // Routines for DXF export //----------------------------------------------------------------------------- class DxfWriteInterface : public DRW_Interface { +public: DxfFileWriter *writer; - dxfRW *dxf; + dxfRW *dxf; + + std::set messages; static DRW_Coord toCoord(const Vector &v) { return DRW_Coord(v.x, v.y, v.z); @@ -156,18 +24,14 @@ class DxfWriteInterface : public DRW_Interface { return writer->Transform(v); } -public: - DxfWriteInterface(DxfFileWriter *w, dxfRW *dxfrw) : - writer(w), dxf(dxfrw) {} - - virtual void writeTextstyles() { + void writeTextstyles() override { DRW_Textstyle ts; ts.name = "unicode"; ts.font = "unicode"; dxf->writeTextstyle(&ts); } - virtual void writeLayers() { + void writeLayers() override { DRW_Layer layer; layer.name = "dimensions"; @@ -190,40 +54,46 @@ public: } } - virtual void writeLTypes() { - for(int i = 0; i <= Style::LAST_STIPPLE; i++) { + void writeLTypes() override { + for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) { + StipplePattern st = (StipplePattern)i; DRW_LType type; // LibreCAD requires the line type to have one of these exact names, // or otherwise it overwrites it with its own (continuous) style. - type.name = DxfFileWriter::lineTypeName(i); + type.name = DxfFileWriter::lineTypeName(st); double sw = 1.0; - switch(i) { - case Style::STIPPLE_CONTINUOUS: + switch(st) { + case StipplePattern::CONTINUOUS: + break; + + case StipplePattern::SHORT_DASH: + type.path.push_back(sw); + type.path.push_back(-sw * 2.0); break; - case Style::STIPPLE_DASH: + case StipplePattern::DASH: type.path.push_back(sw); type.path.push_back(-sw); break; - case Style::STIPPLE_LONG_DASH: + case StipplePattern::LONG_DASH: type.path.push_back(sw * 2.0); type.path.push_back(-sw); break; - case Style::STIPPLE_DASH_DOT: + case StipplePattern::DASH_DOT: type.path.push_back(sw); type.path.push_back(-sw); type.path.push_back(0.0); type.path.push_back(-sw); break; - case Style::STIPPLE_DOT: + case StipplePattern::DOT: type.path.push_back(sw); type.path.push_back(0.0); break; - case Style::STIPPLE_DASH_DOT_DOT: + case StipplePattern::DASH_DOT_DOT: type.path.push_back(sw); type.path.push_back(-sw); type.path.push_back(0.0); @@ -231,6 +101,11 @@ public: type.path.push_back(0.0); type.path.push_back(-sw); break; + + case StipplePattern::FREEHAND: + case StipplePattern::ZIGZAG: + // Not implemented; exported as continuous. + break; } dxf->writeLineType(&type); } @@ -242,49 +117,49 @@ public: for(DxfFileWriter::BezierPath &path : writer->paths) { for(SBezier *sb : path.beziers) { if(sb->deg != 1) continue; - builder.addEdge(sb->ctrl[0], sb->ctrl[1], sb->auxA); + builder.AddEdge(sb->ctrl[0], sb->ctrl[1], (uint32_t)sb->auxA); } } - bool found; - bool loop = false; - do { - found = false; - for(PolylineBuilder::Edge *e : builder.edges) { - if(e->tag != 0) continue; - - PolylineBuilder::Vertex *start; - PolylineBuilder::Vertex *next; - if(!e->getStartAndNext(&start, &next, loop)) continue; - found = true; - e->tag = 1; - - DRW_Polyline polyline; - assignEntityDefaults(&polyline, e->style); - polyline.vertlist.push_back( - new DRW_Vertex(start->pos.x, start->pos.y, start->pos.z, 0.0)); - polyline.vertlist.push_back( - new DRW_Vertex(next->pos.x, next->pos.y, next->pos.z, 0.0)); - while(next->getNext(e->style, &next)) { - polyline.vertlist.push_back( - new DRW_Vertex(next->pos.x, next->pos.y, next->pos.z, 0.0)); - } - dxf->writePolyline(&polyline); - } + DRW_Polyline polyline; - if(!found && !loop) { - loop = true; - found = true; + auto startFunc = [&](PolylineBuilder::Vertex *start, + PolylineBuilder::Vertex *next, + PolylineBuilder::Edge *e) { + hStyle hs = { e->kind }; + polyline = {}; + assignEntityDefaults(&polyline, hs); + + if(!(EXACT(start->pos.z == 0.0) && EXACT(next->pos.z == 0.0))) { + polyline.flags |= 8 /* 3d polyline */; + } + polyline.vertlist.push_back( + new DRW_Vertex(start->pos.x, start->pos.y, start->pos.z, 0.0)); + polyline.vertlist.push_back( + new DRW_Vertex(next->pos.x, next->pos.y, next->pos.z, 0.0)); + }; + + auto nextFunc = [&](PolylineBuilder::Vertex *next, PolylineBuilder::Edge *e) { + if(!EXACT(next->pos.z == 0.0)) { + polyline.flags |= 8 /* 3d polyline */; } - } while(found); + polyline.vertlist.push_back( + new DRW_Vertex(next->pos.x, next->pos.y, next->pos.z, 0.0)); + }; - for(PolylineBuilder::Edge *e : builder.edges) { - if(e->tag != 0) continue; - writeLine(e->a->pos, e->b->pos, e->style); - } + auto endFunc = [&]() { + dxf->writePolyline(&polyline); + }; + + auto aloneFunc = [&](PolylineBuilder::Edge *e) { + hStyle hs = { e->kind }; + writeLine(e->a->pos, e->b->pos, hs); + }; + + builder.Generate(startFunc, nextFunc, aloneFunc, endFunc); } - virtual void writeEntities() { + void writeEntities() override { writePolylines(); for(DxfFileWriter::BezierPath &path : writer->paths) { @@ -299,7 +174,7 @@ public: for(c = writer->constraint->First(); c; c = writer->constraint->NextAfter(c)) { if(!writer->NeedToOutput(c)) continue; switch(c->type) { - case Constraint::PT_PT_DISTANCE: { + case Constraint::Type::PT_PT_DISTANCE: { Vector ap = SK.GetEntity(c->ptA)->PointGetNum(); Vector bp = SK.GetEntity(c->ptB)->PointGetNum(); Vector ref = ((ap.Plus(bp)).ScaledBy(0.5)).Plus(c->disp.offset); @@ -308,7 +183,7 @@ public: break; } - case Constraint::PT_LINE_DISTANCE: { + case Constraint::Type::PT_LINE_DISTANCE: { Vector pt = SK.GetEntity(c->ptA)->PointGetNum(); Entity *line = SK.GetEntity(c->entityA); Vector lA = SK.GetEntity(line->point[0])->PointGetNum(); @@ -340,7 +215,7 @@ public: break; } - case Constraint::DIAMETER: { + case Constraint::Type::DIAMETER: { Entity *circle = SK.GetEntity(c->entityA); Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); Quaternion q = SK.GetEntity(circle->normal)->NormalGetNum(); @@ -364,7 +239,7 @@ public: break; } - case Constraint::ANGLE: { + case Constraint::Type::ANGLE: { Entity *a = SK.GetEntity(c->entityA); Entity *b = SK.GetEntity(c->entityB); @@ -404,7 +279,7 @@ public: ref = pi.Plus(bisect.WithMagnitude(c->disp.offset.Magnitude())); - // Get lines agian to write exact line. + // Get lines again to write exact line. a0 = a->VectorGetStartPoint(); b0 = b->VectorGetStartPoint(); da = a->VectorGetNum(); @@ -416,13 +291,17 @@ public: break; } - case Constraint::COMMENT: { + case Constraint::Type::COMMENT: { Style *st = SK.style.FindById(c->GetStyle()); writeText(xfrm(c->disp.offset), c->Label(), Style::TextHeight(c->GetStyle()) / SS.GW.scale, st->textAngle, st->textOrigin, c->GetStyle()); break; } + + default: + // Other types of constraints do not have a DXF dimension equivalent. + break; } } } @@ -446,13 +325,19 @@ public: void assignEntityDefaults(DRW_Entity *entity, hStyle hs) { Style *s = Style::Get(hs); - RgbaColor color = s->Color(hs, true); + RgbaColor color = Style::Color(hs, /*forExport=*/true); entity->color24 = color.ToPackedIntBGRA(); entity->color = findDxfColor(color); entity->layer = s->DescriptionString(); entity->lineType = DxfFileWriter::lineTypeName(s->stippleType); entity->ltypeScale = Style::StippleScaleMm(s->h); entity->setWidthMm(Style::WidthMm(hs.v)); + + if(s->stippleType == StipplePattern::FREEHAND) { + messages.insert(_("freehand lines were replaced with continuous lines")); + } else if(s->stippleType == StipplePattern::ZIGZAG) { + messages.insert(_("zigzag lines were replaced with continuous lines")); + } } void assignDimensionDefaults(DRW_Dimension *dimension, hStyle hs) { @@ -485,7 +370,7 @@ public: DRW_Polyline polyline; assignEntityDefaults(&polyline, hs); for(int i = 0; i < lv.n; i++) { - Vector *v = &lv.elem[i]; + Vector *v = &lv[i]; DRW_Vertex *vertex = new DRW_Vertex(v->x, v->y, v->z, 0.0); polyline.vertlist.push_back(vertex); } @@ -513,9 +398,7 @@ public: spline->knotslist.push_back(1.0); spline->knotslist.push_back(1.0); spline->knotslist.push_back(1.0); - } else { - oops(); - } + } else ssassert(false, "Unexpected degree of spline"); } void writeSpline(SBezier *sb) { @@ -556,7 +439,7 @@ public: // Rational bezier // We'd like to export rational beziers exactly, but the resulting DXF // files can only be read by AutoCAD; LibreCAD/QCad simply do not - // implement the feature. So, export as piecewise linear for compatiblity. + // implement the feature. So, export as piecewise linear for compatibility. writeBezierAsPwl(sb); } else { // Any other curve @@ -633,7 +516,7 @@ public: } void writeText(Vector textp, const std::string &text, - double height, double angle, int origin, hStyle hs) { + double height, double angle, Style::TextOrigin origin, hStyle hs) { DRW_Text txt; assignEntityDefaults(&txt, hs); txt.layer = "text"; @@ -644,15 +527,15 @@ public: txt.height = height; txt.angle = angle; txt.alignH = DRW_Text::HCenter; - if(origin & Style::ORIGIN_LEFT) { + if((uint32_t)origin & (uint32_t)Style::TextOrigin::LEFT) { txt.alignH = DRW_Text::HLeft; - } else if(origin & Style::ORIGIN_RIGHT) { + } else if((uint32_t)origin & (uint32_t)Style::TextOrigin::RIGHT) { txt.alignH = DRW_Text::HRight; } txt.alignV = DRW_Text::VMiddle; - if(origin & Style::ORIGIN_TOP) { + if((uint32_t)origin & (uint32_t)Style::TextOrigin::TOP) { txt.alignV = DRW_Text::VTop; - } else if(origin & Style::ORIGIN_BOT) { + } else if((uint32_t)origin & (uint32_t)Style::TextOrigin::BOT) { txt.alignV = DRW_Text::VBaseLine; } dxf->writeText(&txt); @@ -664,10 +547,13 @@ bool DxfFileWriter::OutputConstraints(IdList *constraint return true; } -void DxfFileWriter::StartFile(void) { +void DxfFileWriter::StartFile() { paths.clear(); } +void DxfFileWriter::Background(RgbaColor color) { +} + void DxfFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { @@ -689,40 +575,59 @@ void DxfFileWriter::Bezier(SBezier *sb) { void DxfFileWriter::FinishAndCloseFile() { dxfRW dxf; - DxfWriteInterface interface(this, &dxf); + DxfWriteInterface interface = {}; + interface.writer = this; + interface.dxf = &dxf; + std::stringstream stream; dxf.write(stream, &interface, DRW::AC1021, /*bin=*/false); paths.clear(); constraint = NULL; if(!WriteFile(filename, stream.str())) { - Error("Couldn't write to '%s'", filename.c_str()); + Error("Couldn't write to '%s'", filename.raw.c_str()); return; } + + if(!interface.messages.empty()) { + std::string text = _("Some aspects of the drawing have no DXF equivalent and " + "were not exported:\n"); + for(const std::string &message : interface.messages) { + text += " * " + message + "\n"; + } + Message(text.c_str()); + } } bool DxfFileWriter::NeedToOutput(Constraint *c) { switch(c->type) { - case Constraint::PT_PT_DISTANCE: - case Constraint::PT_LINE_DISTANCE: - case Constraint::DIAMETER: - case Constraint::ANGLE: - case Constraint::COMMENT: + case Constraint::Type::PT_PT_DISTANCE: + case Constraint::Type::PT_LINE_DISTANCE: + case Constraint::Type::DIAMETER: + case Constraint::Type::ANGLE: + case Constraint::Type::COMMENT: return c->IsVisible(); + + default: // See writeEntities(). + break; } return false; } -const char *DxfFileWriter::lineTypeName(int stippleType) { +const char *DxfFileWriter::lineTypeName(StipplePattern stippleType) { switch(stippleType) { - case Style::STIPPLE_CONTINUOUS: return "CONTINUOUS"; - case Style::STIPPLE_DASH: return "DASHED"; - case Style::STIPPLE_LONG_DASH: return "DASHEDX2"; - case Style::STIPPLE_DASH_DOT: return "DASHDOT"; - case Style::STIPPLE_DASH_DOT_DOT: return "DIVIDE"; - case Style::STIPPLE_DOT: return "DOT"; - case Style::STIPPLE_FREEHAND: return "CONTINUOUS"; - case Style::STIPPLE_ZIGZAG: return "CONTINUOUS"; + case StipplePattern::CONTINUOUS: return "CONTINUOUS"; + case StipplePattern::SHORT_DASH: return "DASHED"; + case StipplePattern::DASH: return "DASHED"; + case StipplePattern::LONG_DASH: return "DASHEDX2"; + case StipplePattern::DASH_DOT: return "DASHDOT"; + case StipplePattern::DASH_DOT_DOT: return "DIVIDE"; + case StipplePattern::DOT: return "DOT"; + + case StipplePattern::FREEHAND: + case StipplePattern::ZIGZAG: + /* no corresponding DXF line type */ + break; } return "CONTINUOUS"; @@ -732,7 +637,7 @@ const char *DxfFileWriter::lineTypeName(int stippleType) { // Routines for EPS output //----------------------------------------------------------------------------- -static std::string MakeStipplePattern(int pattern, double scale, char delimiter, +static std::string MakeStipplePattern(StipplePattern pattern, double scale, char delimiter, bool inkscapeWorkaround = false) { scale /= 2.0; @@ -742,38 +647,40 @@ static std::string MakeStipplePattern(int pattern, double scale, char delimiter, std::string result; switch(pattern) { - case Style::STIPPLE_CONTINUOUS: - case Style::STIPPLE_FREEHAND: - case Style::STIPPLE_ZIGZAG: + case StipplePattern::CONTINUOUS: return ""; - case Style::STIPPLE_DASH: + case StipplePattern::SHORT_DASH: + result = ssprintf("%.3f_%.3f", scale, scale * 2.0); + break; + case StipplePattern::DASH: result = ssprintf("%.3f_%.3f", scale, scale); break; - case Style::STIPPLE_DASH_DOT: + case StipplePattern::DASH_DOT: result = ssprintf("%.3f_%.3f_%.6f_%.3f", scale, scale * 0.5, zero, scale * 0.5); break; - case Style::STIPPLE_DASH_DOT_DOT: + case StipplePattern::DASH_DOT_DOT: result = ssprintf("%.3f_%.3f_%.6f_%.3f_%.6f_%.3f", scale, scale * 0.5, zero, scale * 0.5, scale * 0.5, zero); break; - case Style::STIPPLE_DOT: + case StipplePattern::DOT: result = ssprintf("%.6f_%.3f", zero, scale * 0.5); break; - case Style::STIPPLE_LONG_DASH: + case StipplePattern::LONG_DASH: result = ssprintf("%.3f_%.3f", scale * 2.0, scale * 0.5); break; - default: - oops(); + case StipplePattern::FREEHAND: + case StipplePattern::ZIGZAG: + ssassert(false, "Freehand and zigzag export not implemented"); } std::replace(result.begin(), result.end(), '_', delimiter); return result; } -void EpsFileWriter::StartFile(void) { +void EpsFileWriter::StartFile() { fprintf(f, "%%!PS-Adobe-2.0\r\n" "%%%%Creator: SolveSpace\r\n" @@ -792,6 +699,35 @@ void EpsFileWriter::StartFile(void) { MmToPts(ptMax.y - ptMin.y)); } +void EpsFileWriter::Background(RgbaColor color) { + double width = ptMax.x - ptMin.x; + double height = ptMax.y - ptMin.y; + + fprintf(f, +"%.3f %.3f %.3f setrgbcolor\r\n" +"newpath\r\n" +" %.3f %.3f moveto\r\n" +" %.3f %.3f lineto\r\n" +" %.3f %.3f lineto\r\n" +" %.3f %.3f lineto\r\n" +" closepath\r\n" +"gsave fill grestore\r\n", + color.redF(), color.greenF(), color.blueF(), + MmToPts(0), MmToPts(0), + MmToPts(width), MmToPts(0), + MmToPts(width), MmToPts(height), + MmToPts(0), MmToPts(height)); + + // same issue with cracks, stroke it to avoid them + double sw = max(width, height) / 1000; + fprintf(f, +"1 setlinejoin\r\n" +"1 setlinecap\r\n" +"%.3f setlinewidth\r\n" +"gsave stroke grestore\r\n", + MmToPts(sw)); +} + void EpsFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { @@ -801,7 +737,7 @@ void EpsFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, void EpsFileWriter::FinishPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { - int pattern = Style::PatternType(hs); + StipplePattern pattern = Style::PatternType(hs); double stippleScale = MmToPts(Style::StippleScaleMm(hs)); fprintf(f, " %.3f setlinewidth\r\n" @@ -812,8 +748,7 @@ void EpsFileWriter::FinishPath(RgbaColor strokeRgb, double lineWidth, " gsave stroke grestore\r\n", MmToPts(lineWidth), strokeRgb.redF(), strokeRgb.greenF(), strokeRgb.blueF(), - MakeStipplePattern(pattern, stippleScale, ' ').c_str() - ); + MakeStipplePattern(pattern, stippleScale, ' ').c_str()); if(filled) { fprintf(f, " %.3f %.3f %.3f setrgbcolor\r\n" " gsave fill grestore\r\n", @@ -885,7 +820,7 @@ void EpsFileWriter::Bezier(SBezier *sb) { } } -void EpsFileWriter::FinishAndCloseFile(void) { +void EpsFileWriter::FinishAndCloseFile() { fprintf(f, "\r\n" "grestore\r\n" @@ -897,12 +832,12 @@ void EpsFileWriter::FinishAndCloseFile(void) { // Routines for PDF output, some extra complexity because we have to generate // a correct xref table. //----------------------------------------------------------------------------- -void PdfFileWriter::StartFile(void) { +void PdfFileWriter::StartFile() { if((ptMax.x - ptMin.x) > 200*25.4 || (ptMax.y - ptMin.y) > 200*25.4) { - Message("PDF page size exceeds 200 by 200 inches; many viewers may " - "reject this file."); + Message(_("PDF page size exceeds 200 by 200 inches; many viewers may " + "reject this file.")); } fprintf(f, @@ -959,7 +894,7 @@ void PdfFileWriter::StartFile(void) { bodyStart = (uint32_t)ftell(f); } -void PdfFileWriter::FinishAndCloseFile(void) { +void PdfFileWriter::FinishAndCloseFile() { uint32_t bodyEnd = (uint32_t)ftell(f); fprintf(f, @@ -1023,10 +958,34 @@ void PdfFileWriter::FinishAndCloseFile(void) { } +void PdfFileWriter::Background(RgbaColor color) { + double width = ptMax.x - ptMin.x; + double height = ptMax.y - ptMin.y; + double sw = max(width, height) / 1000; + + fprintf(f, +"1 J 1 j\r\n" +"%.3f %.3f %.3f RG\r\n" +"%.3f %.3f %.3f rg\r\n" +"%.3f w\r\n" +"%.3f %.3f m\r\n" +"%.3f %.3f l\r\n" +"%.3f %.3f l\r\n" +"%.3f %.3f l\r\n" +"b\r\n", + color.redF(), color.greenF(), color.blueF(), + color.redF(), color.greenF(), color.blueF(), + MmToPts(sw), + MmToPts(0), MmToPts(0), + MmToPts(width), MmToPts(0), + MmToPts(width), MmToPts(height), + MmToPts(0), MmToPts(height)); +} + void PdfFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { - int pattern = Style::PatternType(hs); + StipplePattern pattern = Style::PatternType(hs); double stippleScale = MmToPts(Style::StippleScaleMm(hs)); fprintf(f, "1 J 1 j " // round endcaps and joins @@ -1101,7 +1060,7 @@ void PdfFileWriter::Bezier(SBezier *sb) { //----------------------------------------------------------------------------- // Routines for SVG output //----------------------------------------------------------------------------- -void SvgFileWriter::StartFile(void) { +void SvgFileWriter::StartFile() { fprintf(f, "\r\n" @@ -1124,10 +1083,11 @@ void SvgFileWriter::StartFile(void) { double sw = max(ptMax.x - ptMin.x, ptMax.y - ptMin.y) / 1000; fprintf(f, "stroke-width:%f;\r\n", sw); fprintf(f, "}\r\n"); - for(int i = 0; i < SK.style.n; i++) { - Style *s = &SK.style.elem[i]; - RgbaColor strokeRgb = Style::Color(s->h, true); - int pattern = Style::PatternType(s->h); + for(auto &style : SK.style) { + Style *s = &style; + + RgbaColor strokeRgb = Style::Color(s->h, /*forExport=*/true); + StipplePattern pattern = Style::PatternType(s->h); double stippleScale = Style::StippleScaleMm(s->h); fprintf(f, ".s%x {\r\n", s->h.v); @@ -1147,6 +1107,16 @@ void SvgFileWriter::StartFile(void) { fprintf(f, "]]>\r\n"); } +void SvgFileWriter::Background(RgbaColor color) { + fprintf(f, +"\r\n", + color.red, color.green, color.blue); +} + void SvgFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { @@ -1225,7 +1195,7 @@ void SvgFileWriter::Bezier(SBezier *sb) { } } -void SvgFileWriter::FinishAndCloseFile(void) { +void SvgFileWriter::FinishAndCloseFile() { fprintf(f, "\r\n\r\n"); fclose(f); } @@ -1237,11 +1207,14 @@ double HpglFileWriter::MmToHpglUnits(double mm) { return mm*40; } -void HpglFileWriter::StartFile(void) { +void HpglFileWriter::StartFile() { fprintf(f, "IN;\r\n"); fprintf(f, "SP1;\r\n"); } +void HpglFileWriter::Background(RgbaColor color) { +} + void HpglFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { @@ -1267,7 +1240,7 @@ void HpglFileWriter::Bezier(SBezier *sb) { } } -void HpglFileWriter::FinishAndCloseFile(void) { +void HpglFileWriter::FinishAndCloseFile() { fclose(f); } @@ -1276,13 +1249,15 @@ void HpglFileWriter::FinishAndCloseFile(void) { // multiple passes, and to specify the feeds and depth; those parameters get // set in the configuration screen. //----------------------------------------------------------------------------- -void GCodeFileWriter::StartFile(void) { +void GCodeFileWriter::StartFile() { sel = {}; } void GCodeFileWriter::StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { } +void GCodeFileWriter::Background(RgbaColor color) { +} void GCodeFileWriter::FinishPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) { @@ -1298,7 +1273,7 @@ void GCodeFileWriter::Bezier(SBezier *sb) { } } -void GCodeFileWriter::FinishAndCloseFile(void) { +void GCodeFileWriter::FinishAndCloseFile() { SPolygon sp = {}; sel.AssemblePolygon(&sp, NULL); @@ -1338,12 +1313,15 @@ void GCodeFileWriter::FinishAndCloseFile(void) { // Routine for STEP output; just a wrapper around the general STEP stuff that // can also be used for surfaces or 3d curves. //----------------------------------------------------------------------------- -void Step2dFileWriter::StartFile(void) { +void Step2dFileWriter::StartFile() { sfw = {}; sfw.f = f; sfw.WriteHeader(); } +void Step2dFileWriter::Background(RgbaColor color) { +} + void Step2dFileWriter::Triangle(STriangle *tr) { } @@ -1361,7 +1339,7 @@ void Step2dFileWriter::Bezier(SBezier *sb) { sfw.curves.Add(&c); } -void Step2dFileWriter::FinishAndCloseFile(void) { +void Step2dFileWriter::FinishAndCloseFile() { sfw.WriteWireframe(); sfw.WriteFooter(); fclose(f); diff --git a/src/expr.cpp b/src/expr.cpp index 52e06b0..99d68ec 100644 --- a/src/expr.cpp +++ b/src/expr.cpp @@ -37,7 +37,7 @@ ExprVector ExprVector::From(double x, double y, double z) { return ve; } -ExprVector ExprVector::Minus(ExprVector b) { +ExprVector ExprVector::Minus(ExprVector b) const { ExprVector r; r.x = x->Minus(b.x); r.y = y->Minus(b.y); @@ -45,7 +45,7 @@ ExprVector ExprVector::Minus(ExprVector b) { return r; } -ExprVector ExprVector::Plus(ExprVector b) { +ExprVector ExprVector::Plus(ExprVector b) const { ExprVector r; r.x = x->Plus(b.x); r.y = y->Plus(b.y); @@ -53,7 +53,7 @@ ExprVector ExprVector::Plus(ExprVector b) { return r; } -Expr *ExprVector::Dot(ExprVector b) { +Expr *ExprVector::Dot(ExprVector b) const { Expr *r; r = x->Times(b.x); r = r->Plus(y->Times(b.y)); @@ -61,7 +61,7 @@ Expr *ExprVector::Dot(ExprVector b) { return r; } -ExprVector ExprVector::Cross(ExprVector b) { +ExprVector ExprVector::Cross(ExprVector b) const { ExprVector r; r.x = (y->Times(b.z))->Minus(z->Times(b.y)); r.y = (z->Times(b.x))->Minus(x->Times(b.z)); @@ -69,7 +69,7 @@ ExprVector ExprVector::Cross(ExprVector b) { return r; } -ExprVector ExprVector::ScaledBy(Expr *s) { +ExprVector ExprVector::ScaledBy(Expr *s) const { ExprVector r; r.x = x->Times(s); r.y = y->Times(s); @@ -77,12 +77,12 @@ ExprVector ExprVector::ScaledBy(Expr *s) { return r; } -ExprVector ExprVector::WithMagnitude(Expr *s) { +ExprVector ExprVector::WithMagnitude(Expr *s) const { Expr *m = Magnitude(); return ScaledBy(s->Div(m)); } -Expr *ExprVector::Magnitude(void) { +Expr *ExprVector::Magnitude() const { Expr *r; r = x->Square(); r = r->Plus(y->Square()); @@ -90,7 +90,7 @@ Expr *ExprVector::Magnitude(void) { return r->Sqrt(); } -Vector ExprVector::Eval(void) { +Vector ExprVector::Eval() const { Vector r; r.x = x->Eval(); r.y = y->Eval(); @@ -126,7 +126,7 @@ ExprQuaternion ExprQuaternion::From(Quaternion qn) { return qe; } -ExprVector ExprQuaternion::RotationU(void) { +ExprVector ExprQuaternion::RotationU() const { ExprVector u; Expr *two = Expr::From(2); @@ -144,7 +144,7 @@ ExprVector ExprQuaternion::RotationU(void) { return u; } -ExprVector ExprQuaternion::RotationV(void) { +ExprVector ExprQuaternion::RotationV() const { ExprVector v; Expr *two = Expr::From(2); @@ -162,7 +162,7 @@ ExprVector ExprQuaternion::RotationV(void) { return v; } -ExprVector ExprQuaternion::RotationN(void) { +ExprVector ExprQuaternion::RotationN() const { ExprVector n; Expr *two = Expr::From(2); @@ -180,14 +180,14 @@ ExprVector ExprQuaternion::RotationN(void) { return n; } -ExprVector ExprQuaternion::Rotate(ExprVector p) { +ExprVector ExprQuaternion::Rotate(ExprVector p) const { // Express the point in the new basis return (RotationU().ScaledBy(p.x)).Plus( RotationV().ScaledBy(p.y)).Plus( RotationN().ScaledBy(p.z)); } -ExprQuaternion ExprQuaternion::Times(ExprQuaternion b) { +ExprQuaternion ExprQuaternion::Times(ExprQuaternion b) const { Expr *sa = w, *sb = b.w; ExprVector va = { vx, vy, vz }; ExprVector vb = { b.vx, b.vy, b.vz }; @@ -203,7 +203,7 @@ ExprQuaternion ExprQuaternion::Times(ExprQuaternion b) { return r; } -Expr *ExprQuaternion::Magnitude(void) { +Expr *ExprQuaternion::Magnitude() const { return ((w ->Square())->Plus( (vx->Square())->Plus( (vy->Square())->Plus( @@ -213,7 +213,7 @@ Expr *ExprQuaternion::Magnitude(void) { Expr *Expr::From(hParam p) { Expr *r = AllocExpr(); - r->op = PARAM; + r->op = Op::PARAM; r->parh = p; return r; } @@ -249,12 +249,12 @@ Expr *Expr::From(double v) { } Expr *r = AllocExpr(); - r->op = CONSTANT; + r->op = Op::CONSTANT; r->v = v; return r; } -Expr *Expr::AnyOp(int newOp, Expr *b) { +Expr *Expr::AnyOp(Op newOp, Expr *b) { Expr *r = AllocExpr(); r->op = newOp; r->a = this; @@ -262,42 +262,42 @@ Expr *Expr::AnyOp(int newOp, Expr *b) { return r; } -int Expr::Children(void) { +int Expr::Children() const { switch(op) { - case PARAM: - case PARAM_PTR: - case CONSTANT: + case Op::PARAM: + case Op::PARAM_PTR: + case Op::CONSTANT: + case Op::VARIABLE: return 0; - case PLUS: - case MINUS: - case TIMES: - case DIV: + case Op::PLUS: + case Op::MINUS: + case Op::TIMES: + case Op::DIV: return 2; - case NEGATE: - case SQRT: - case SQUARE: - case SIN: - case COS: - case ASIN: - case ACOS: + case Op::NEGATE: + case Op::SQRT: + case Op::SQUARE: + case Op::SIN: + case Op::COS: + case Op::ASIN: + case Op::ACOS: return 1; - - default: oops(); } + ssassert(false, "Unexpected operation"); } -int Expr::Nodes(void) { +int Expr::Nodes() const { switch(Children()) { case 0: return 1; case 1: return 1 + a->Nodes(); case 2: return 1 + a->Nodes() + b->Nodes(); - default: oops(); + default: ssassert(false, "Unexpected children count"); } } -Expr *Expr::DeepCopy(void) { +Expr *Expr::DeepCopy() const { Expr *n = AllocExpr(); *n = *this; int c = n->Children(); @@ -307,20 +307,20 @@ Expr *Expr::DeepCopy(void) { } Expr *Expr::DeepCopyWithParamsAsPointers(IdList *firstTry, - IdList *thenTry) + IdList *thenTry) const { Expr *n = AllocExpr(); - if(op == PARAM) { + if(op == Op::PARAM) { // A param that is referenced by its hParam gets rewritten to go // straight in to the parameter table with a pointer, or simply // into a constant if it's already known. Param *p = firstTry->FindByIdNoOops(parh); if(!p) p = thenTry->FindById(parh); if(p->known) { - n->op = CONSTANT; + n->op = Op::CONSTANT; n->v = p->val; } else { - n->op = PARAM_PTR; + n->op = Op::PARAM_PTR; n->parp = p; } return n; @@ -333,77 +333,77 @@ Expr *Expr::DeepCopyWithParamsAsPointers(IdList *firstTry, return n; } -double Expr::Eval(void) { +double Expr::Eval() const { switch(op) { - case PARAM: return SK.GetParam(parh)->val; - case PARAM_PTR: return parp->val; - - case CONSTANT: return v; - - case PLUS: return a->Eval() + b->Eval(); - case MINUS: return a->Eval() - b->Eval(); - case TIMES: return a->Eval() * b->Eval(); - case DIV: return a->Eval() / b->Eval(); - - case NEGATE: return -(a->Eval()); - case SQRT: return sqrt(a->Eval()); - case SQUARE: { double r = a->Eval(); return r*r; } - case SIN: return sin(a->Eval()); - case COS: return cos(a->Eval()); - case ACOS: return acos(a->Eval()); - case ASIN: return asin(a->Eval()); - - default: oops(); + case Op::PARAM: return SK.GetParam(parh)->val; + case Op::PARAM_PTR: return parp->val; + + case Op::CONSTANT: return v; + case Op::VARIABLE: ssassert(false, "Not supported yet"); + + case Op::PLUS: return a->Eval() + b->Eval(); + case Op::MINUS: return a->Eval() - b->Eval(); + case Op::TIMES: return a->Eval() * b->Eval(); + case Op::DIV: return a->Eval() / b->Eval(); + + case Op::NEGATE: return -(a->Eval()); + case Op::SQRT: return sqrt(a->Eval()); + case Op::SQUARE: { double r = a->Eval(); return r*r; } + case Op::SIN: return sin(a->Eval()); + case Op::COS: return cos(a->Eval()); + case Op::ACOS: return acos(a->Eval()); + case Op::ASIN: return asin(a->Eval()); } + ssassert(false, "Unexpected operation"); } -Expr *Expr::PartialWrt(hParam p) { +Expr *Expr::PartialWrt(hParam p) const { Expr *da, *db; switch(op) { - case PARAM_PTR: return From(p.v == parp->h.v ? 1 : 0); - case PARAM: return From(p.v == parh.v ? 1 : 0); + case Op::PARAM_PTR: return From(p == parp->h ? 1 : 0); + case Op::PARAM: return From(p == parh ? 1 : 0); - case CONSTANT: return From(0.0); + case Op::CONSTANT: return From(0.0); + case Op::VARIABLE: ssassert(false, "Not supported yet"); - case PLUS: return (a->PartialWrt(p))->Plus(b->PartialWrt(p)); - case MINUS: return (a->PartialWrt(p))->Minus(b->PartialWrt(p)); + case Op::PLUS: return (a->PartialWrt(p))->Plus(b->PartialWrt(p)); + case Op::MINUS: return (a->PartialWrt(p))->Minus(b->PartialWrt(p)); - case TIMES: + case Op::TIMES: da = a->PartialWrt(p); db = b->PartialWrt(p); return (a->Times(db))->Plus(b->Times(da)); - case DIV: + case Op::DIV: da = a->PartialWrt(p); db = b->PartialWrt(p); return ((da->Times(b))->Minus(a->Times(db)))->Div(b->Square()); - case SQRT: + case Op::SQRT: return (From(0.5)->Div(a->Sqrt()))->Times(a->PartialWrt(p)); - case SQUARE: + case Op::SQUARE: return (From(2.0)->Times(a))->Times(a->PartialWrt(p)); - case NEGATE: return (a->PartialWrt(p))->Negate(); - case SIN: return (a->Cos())->Times(a->PartialWrt(p)); - case COS: return ((a->Sin())->Times(a->PartialWrt(p)))->Negate(); + case Op::NEGATE: return (a->PartialWrt(p))->Negate(); + case Op::SIN: return (a->Cos())->Times(a->PartialWrt(p)); + case Op::COS: return ((a->Sin())->Times(a->PartialWrt(p)))->Negate(); - case ASIN: + case Op::ASIN: return (From(1)->Div((From(1)->Minus(a->Square()))->Sqrt())) ->Times(a->PartialWrt(p)); - case ACOS: + case Op::ACOS: return (From(-1)->Div((From(1)->Minus(a->Square()))->Sqrt())) ->Times(a->PartialWrt(p)); - - default: oops(); } + ssassert(false, "Unexpected operation"); } -uint64_t Expr::ParamsUsed(void) { +uint64_t Expr::ParamsUsed() const { uint64_t r = 0; - if(op == PARAM) r |= ((uint64_t)1 << (parh.v % 61)); - if(op == PARAM_PTR) r |= ((uint64_t)1 << (parp->h.v % 61)); + if(op == Op::PARAM) r |= ((uint64_t)1 << (parh.v % 61)); + if(op == Op::PARAM_PTR) r |= ((uint64_t)1 << (parp->h.v % 61)); int c = Children(); if(c >= 1) r |= a->ParamsUsed(); @@ -411,9 +411,9 @@ uint64_t Expr::ParamsUsed(void) { return r; } -bool Expr::DependsOn(hParam p) { - if(op == PARAM) return (parh.v == p.v); - if(op == PARAM_PTR) return (parp->h.v == p.v); +bool Expr::DependsOn(hParam p) const { + if(op == Op::PARAM) return (parh == p); + if(op == Op::PARAM_PTR) return (parp->h == p); int c = Children(); if(c == 1) return a->DependsOn(p); @@ -424,7 +424,7 @@ bool Expr::DependsOn(hParam p) { bool Expr::Tol(double a, double b) { return fabs(a - b) < 0.001; } -Expr *Expr::FoldConstants(void) { +Expr *Expr::FoldConstants() { Expr *n = AllocExpr(); *n = *this; @@ -433,69 +433,68 @@ Expr *Expr::FoldConstants(void) { if(c >= 2) n->b = b->FoldConstants(); switch(op) { - case PARAM_PTR: - case PARAM: - case CONSTANT: + case Op::PARAM_PTR: + case Op::PARAM: + case Op::CONSTANT: + case Op::VARIABLE: break; - case MINUS: - case TIMES: - case DIV: - case PLUS: + case Op::MINUS: + case Op::TIMES: + case Op::DIV: + case Op::PLUS: // If both ops are known, then we can evaluate immediately - if(n->a->op == CONSTANT && n->b->op == CONSTANT) { + if(n->a->op == Op::CONSTANT && n->b->op == Op::CONSTANT) { double nv = n->Eval(); - n->op = CONSTANT; + n->op = Op::CONSTANT; n->v = nv; break; } // x + 0 = 0 + x = x - if(op == PLUS && n->b->op == CONSTANT && Tol(n->b->v, 0)) { + if(op == Op::PLUS && n->b->op == Op::CONSTANT && Tol(n->b->v, 0)) { *n = *(n->a); break; } - if(op == PLUS && n->a->op == CONSTANT && Tol(n->a->v, 0)) { + if(op == Op::PLUS && n->a->op == Op::CONSTANT && Tol(n->a->v, 0)) { *n = *(n->b); break; } // 1*x = x*1 = x - if(op == TIMES && n->b->op == CONSTANT && Tol(n->b->v, 1)) { + if(op == Op::TIMES && n->b->op == Op::CONSTANT && Tol(n->b->v, 1)) { *n = *(n->a); break; } - if(op == TIMES && n->a->op == CONSTANT && Tol(n->a->v, 1)) { + if(op == Op::TIMES && n->a->op == Op::CONSTANT && Tol(n->a->v, 1)) { *n = *(n->b); break; } // 0*x = x*0 = 0 - if(op == TIMES && n->b->op == CONSTANT && Tol(n->b->v, 0)) { - n->op = CONSTANT; n->v = 0; break; + if(op == Op::TIMES && n->b->op == Op::CONSTANT && Tol(n->b->v, 0)) { + n->op = Op::CONSTANT; n->v = 0; break; } - if(op == TIMES && n->a->op == CONSTANT && Tol(n->a->v, 0)) { - n->op = CONSTANT; n->v = 0; break; + if(op == Op::TIMES && n->a->op == Op::CONSTANT && Tol(n->a->v, 0)) { + n->op = Op::CONSTANT; n->v = 0; break; } break; - case SQRT: - case SQUARE: - case NEGATE: - case SIN: - case COS: - case ASIN: - case ACOS: - if(n->a->op == CONSTANT) { + case Op::SQRT: + case Op::SQUARE: + case Op::NEGATE: + case Op::SIN: + case Op::COS: + case Op::ASIN: + case Op::ACOS: + if(n->a->op == Op::CONSTANT) { double nv = n->Eval(); - n->op = CONSTANT; + n->op = Op::CONSTANT; n->v = nv; } break; - - default: oops(); } return n; } void Expr::Substitute(hParam oldh, hParam newh) { - if(op == PARAM_PTR) oops(); + ssassert(op != Op::PARAM_PTR, "Expected an expression that refer to params via handles"); - if(op == PARAM && parh.v == oldh.v) { + if(op == Op::PARAM && parh == oldh) { parh = newh; } int c = Children(); @@ -510,15 +509,15 @@ void Expr::Substitute(hParam oldh, hParam newh) { //----------------------------------------------------------------------------- const hParam Expr::NO_PARAMS = { 0 }; const hParam Expr::MULTIPLE_PARAMS = { 1 }; -hParam Expr::ReferencedParams(ParamList *pl) { - if(op == PARAM) { +hParam Expr::ReferencedParams(ParamList *pl) const { + if(op == Op::PARAM) { if(pl->FindByIdNoOops(parh)) { return parh; } else { return NO_PARAMS; } } - if(op == PARAM_PTR) oops(); + ssassert(op != Op::PARAM_PTR, "Expected an expression that refer to params via handles"); int c = Children(); if(c == 0) { @@ -529,16 +528,16 @@ hParam Expr::ReferencedParams(ParamList *pl) { hParam pa, pb; pa = a->ReferencedParams(pl); pb = b->ReferencedParams(pl); - if(pa.v == NO_PARAMS.v) { + if(pa == NO_PARAMS) { return pb; - } else if(pb.v == NO_PARAMS.v) { + } else if(pb == NO_PARAMS) { return pa; - } else if(pa.v == pb.v) { + } else if(pa == pb) { return pa; // either, doesn't matter } else { return MULTIPLE_PARAMS; } - } else oops(); + } else ssassert(false, "Unexpected children count"); } @@ -546,33 +545,32 @@ hParam Expr::ReferencedParams(ParamList *pl) { // Routines to pretty-print an expression. Mostly for debugging. //----------------------------------------------------------------------------- -std::string Expr::Print(void) { - +std::string Expr::Print() const { char c; switch(op) { - case PARAM: return ssprintf("param(%08x)", parh.v); - case PARAM_PTR: return ssprintf("param(p%08x)", parp->h.v); + case Op::PARAM: return ssprintf("param(%08x)", parh.v); + case Op::PARAM_PTR: return ssprintf("param(p%08x)", parp->h.v); - case CONSTANT: return ssprintf("%.3f", v); + case Op::CONSTANT: return ssprintf("%.3f", v); + case Op::VARIABLE: return "(var)"; - case PLUS: c = '+'; goto p; - case MINUS: c = '-'; goto p; - case TIMES: c = '*'; goto p; - case DIV: c = '/'; goto p; + case Op::PLUS: c = '+'; goto p; + case Op::MINUS: c = '-'; goto p; + case Op::TIMES: c = '*'; goto p; + case Op::DIV: c = '/'; goto p; p: return "(" + a->Print() + " " + c + " " + b->Print() + ")"; break; - case NEGATE: return "(- " + a->Print() + ")"; - case SQRT: return "(sqrt " + a->Print() + ")"; - case SQUARE: return "(square " + a->Print() + ")"; - case SIN: return "(sin " + a->Print() + ")"; - case COS: return "(cos " + a->Print() + ")"; - case ASIN: return "(asin " + a->Print() + ")"; - case ACOS: return "(acos " + a->Print() + ")"; - - default: oops(); + case Op::NEGATE: return "(- " + a->Print() + ")"; + case Op::SQRT: return "(sqrt " + a->Print() + ")"; + case Op::SQUARE: return "(square " + a->Print() + ")"; + case Op::SIN: return "(sin " + a->Print() + ")"; + case Op::COS: return "(cos " + a->Print() + ")"; + case Op::ASIN: return "(asin " + a->Print() + ")"; + case Op::ACOS: return "(acos " + a->Print() + ")"; } + ssassert(false, "Unexpected operation"); } @@ -583,226 +581,341 @@ p: // to provide calculator type functionality wherever numbers are entered. //----------------------------------------------------------------------------- -#define MAX_UNPARSED 1024 -static Expr *Unparsed[MAX_UNPARSED]; -static int UnparsedCnt, UnparsedP; +class ExprParser { +public: + enum class TokenType { + ERROR = 0, -static Expr *Operands[MAX_UNPARSED]; -static int OperandsP; + PAREN_LEFT, + PAREN_RIGHT, + BINARY_OP, + UNARY_OP, + OPERAND, -static Expr *Operators[MAX_UNPARSED]; -static int OperatorsP; + END, + }; -void Expr::PushOperator(Expr *e) { - if(OperatorsP >= MAX_UNPARSED) throw "operator stack full!"; - Operators[OperatorsP++] = e; -} -Expr *Expr::TopOperator(void) { - if(OperatorsP <= 0) throw "operator stack empty (get top)"; - return Operators[OperatorsP-1]; -} -Expr *Expr::PopOperator(void) { - if(OperatorsP <= 0) throw "operator stack empty (pop)"; - return Operators[--OperatorsP]; -} -void Expr::PushOperand(Expr *e) { - if(OperandsP >= MAX_UNPARSED) throw "operand stack full"; - Operands[OperandsP++] = e; -} -Expr *Expr::PopOperand(void) { - if(OperandsP <= 0) throw "operand stack empty"; - return Operands[--OperandsP]; + class Token { + public: + TokenType type; + Expr *expr; + + static Token From(TokenType type = TokenType::ERROR, Expr *expr = NULL); + static Token From(TokenType type, Expr::Op op); + bool IsError() const { return type == TokenType::ERROR; } + }; + + std::string::const_iterator it, end; + std::vector stack; + + char ReadChar(); + char PeekChar(); + + std::string ReadWord(); + void SkipSpace(); + + Token PopOperator(std::string *error); + Token PopOperand(std::string *error); + + int Precedence(Token token); + Token LexNumber(std::string *error); + Token Lex(std::string *error); + bool Reduce(std::string *error); + bool Parse(std::string *error, size_t reduceUntil = 0); + + static Expr *Parse(const std::string &input, std::string *error); +}; + +ExprParser::Token ExprParser::Token::From(TokenType type, Expr *expr) { + Token t; + t.type = type; + t.expr = expr; + return t; } -Expr *Expr::Next(void) { - if(UnparsedP >= UnparsedCnt) return NULL; - return Unparsed[UnparsedP]; + +ExprParser::Token ExprParser::Token::From(TokenType type, Expr::Op op) { + Token t; + t.type = type; + t.expr = Expr::AllocExpr(); + t.expr->op = op; + return t; } -void Expr::Consume(void) { - if(UnparsedP >= UnparsedCnt) throw "no token to consume"; - UnparsedP++; + +char ExprParser::ReadChar() { + return *it++; } -int Expr::Precedence(Expr *e) { - if(e->op == ALL_RESOLVED) return -1; // never want to reduce this marker - if(e->op != BINARY_OP && e->op != UNARY_OP) oops(); +char ExprParser::PeekChar() { + if(it == end) { + return '\0'; + } else { + return *it; + } +} - switch(e->c) { - case 'q': - case 's': - case 'c': - case 'n': return 30; +std::string ExprParser::ReadWord() { + std::string s; - case '*': - case '/': return 20; + while(char c = PeekChar()) { + if(!isalnum(c)) break; + s.push_back(ReadChar()); + } - case '+': - case '-': return 10; + return s; +} - default: oops(); +void ExprParser::SkipSpace() { + while(char c = PeekChar()) { + if(!isspace(c)) break; + ReadChar(); } } -void Expr::Reduce(void) { - Expr *a, *b; - - Expr *op = PopOperator(); - Expr *n; - int o; - switch(op->c) { - case '+': o = PLUS; goto c; - case '-': o = MINUS; goto c; - case '*': o = TIMES; goto c; - case '/': o = DIV; goto c; -c: - b = PopOperand(); - a = PopOperand(); - n = a->AnyOp(o, b); - break; +ExprParser::Token ExprParser::LexNumber(std::string *error) { + std::string s; - case 'n': n = PopOperand()->Negate(); break; - case 'q': n = PopOperand()->Sqrt(); break; - case 's': n = (PopOperand()->Times(Expr::From(PI/180)))->Sin(); break; - case 'c': n = (PopOperand()->Times(Expr::From(PI/180)))->Cos(); break; - - default: oops(); + while(char c = PeekChar()) { + if(!((c >= '0' && c <= '9') || c == 'e' || c == 'E' || c == '.' || c == '_')) break; + if(c == '_') { + ReadChar(); + continue; + } + s.push_back(ReadChar()); } - PushOperand(n); -} -void Expr::ReduceAndPush(Expr *n) { - while(Precedence(n) <= Precedence(TopOperator())) { - Reduce(); + char *endptr; + double d = strtod(s.c_str(), &endptr); + + Token t = Token::From(); + if(endptr == s.c_str() + s.size()) { + t = Token::From(TokenType::OPERAND, Expr::Op::CONSTANT); + t.expr->v = d; + } else { + *error = "'" + s + "' is not a valid number"; } - PushOperator(n); -} - -void Expr::Parse(void) { - Expr *e = AllocExpr(); - e->op = ALL_RESOLVED; - PushOperator(e); - - for(;;) { - Expr *n = Next(); - if(!n) throw "end of expression unexpected"; - - if(n->op == CONSTANT) { - PushOperand(n); - Consume(); - } else if(n->op == PAREN && n->c == '(') { - Consume(); - Parse(); - n = Next(); - if(n->op != PAREN || n->c != ')') throw "expected: )"; - Consume(); - } else if(n->op == UNARY_OP) { - PushOperator(n); - Consume(); - continue; - } else if(n->op == BINARY_OP && n->c == '-') { - // The minus sign is special, because it might be binary or - // unary, depending on context. - n->op = UNARY_OP; - n->c = 'n'; - PushOperator(n); - Consume(); - continue; + return t; +} + +ExprParser::Token ExprParser::Lex(std::string *error) { + SkipSpace(); + + Token t = Token::From(); + char c = PeekChar(); + if(isupper(c)) { + std::string n = ReadWord(); + t = Token::From(TokenType::OPERAND, Expr::Op::VARIABLE); + } else if(isalpha(c)) { + std::string s = ReadWord(); + if(s == "sqrt") { + t = Token::From(TokenType::UNARY_OP, Expr::Op::SQRT); + } else if(s == "square") { + t = Token::From(TokenType::UNARY_OP, Expr::Op::SQUARE); + } else if(s == "sin") { + t = Token::From(TokenType::UNARY_OP, Expr::Op::SIN); + } else if(s == "cos") { + t = Token::From(TokenType::UNARY_OP, Expr::Op::COS); + } else if(s == "asin") { + t = Token::From(TokenType::UNARY_OP, Expr::Op::ASIN); + } else if(s == "acos") { + t = Token::From(TokenType::UNARY_OP, Expr::Op::ACOS); + } else if(s == "pi") { + t = Token::From(TokenType::OPERAND, Expr::Op::CONSTANT); + t.expr->v = PI; } else { - throw "expected expression"; + *error = "'" + s + "' is not a valid variable, function or constant"; } - - n = Next(); - if(n && n->op == BINARY_OP) { - ReduceAndPush(n); - Consume(); + } else if(isdigit(c) || c == '.') { + return LexNumber(error); + } else if(ispunct(c)) { + ReadChar(); + if(c == '+') { + t = Token::From(TokenType::BINARY_OP, Expr::Op::PLUS); + } else if(c == '-') { + t = Token::From(TokenType::BINARY_OP, Expr::Op::MINUS); + } else if(c == '*') { + t = Token::From(TokenType::BINARY_OP, Expr::Op::TIMES); + } else if(c == '/') { + t = Token::From(TokenType::BINARY_OP, Expr::Op::DIV); + } else if(c == '(') { + t = Token::From(TokenType::PAREN_LEFT); + } else if(c == ')') { + t = Token::From(TokenType::PAREN_RIGHT); } else { - break; + *error = "'" + std::string(1, c) + "' is not a valid operator"; } + } else if(c == '\0') { + t = Token::From(TokenType::END); + } else { + *error = "Unexpected character '" + std::string(1, c) + "'"; } - while(TopOperator()->op != ALL_RESOLVED) { - Reduce(); + return t; +} + +ExprParser::Token ExprParser::PopOperand(std::string *error) { + Token t = Token::From(); + if(stack.empty() || stack.back().type != TokenType::OPERAND) { + *error = "Expected an operand"; + } else { + t = stack.back(); + stack.pop_back(); } - PopOperator(); // discard the ALL_RESOLVED marker + return t; } -void Expr::Lex(const char *in) { - while(*in) { - if(UnparsedCnt >= MAX_UNPARSED) throw "too long"; +ExprParser::Token ExprParser::PopOperator(std::string *error) { + Token t = Token::From(); + if(stack.empty() || (stack.back().type != TokenType::UNARY_OP && + stack.back().type != TokenType::BINARY_OP)) { + *error = "Expected an operator"; + } else { + t = stack.back(); + stack.pop_back(); + } + return t; +} + +int ExprParser::Precedence(Token t) { + ssassert(t.type == TokenType::BINARY_OP || + t.type == TokenType::UNARY_OP || + t.type == TokenType::OPERAND, + "Unexpected token type"); + + if(t.type == TokenType::UNARY_OP) { + return 30; + } else if(t.expr->op == Expr::Op::TIMES || + t.expr->op == Expr::Op::DIV) { + return 20; + } else if(t.expr->op == Expr::Op::PLUS || + t.expr->op == Expr::Op::MINUS) { + return 10; + } else if(t.type == TokenType::OPERAND) { + return 0; + } else ssassert(false, "Unexpected operator"); +} + +bool ExprParser::Reduce(std::string *error) { + Token a = PopOperand(error); + if(a.IsError()) return false; + + Token op = PopOperator(error); + if(op.IsError()) return false; + + Token r = Token::From(TokenType::OPERAND); + switch(op.type) { + case TokenType::BINARY_OP: { + Token b = PopOperand(error); + if(b.IsError()) return false; + r.expr = b.expr->AnyOp(op.expr->op, a.expr); + break; + } - char c = *in; - if(isdigit(c) || c == '.') { - // A number literal - char number[70]; - int len = 0; - while((isdigit(*in) || *in == '.') && len < 30) { - number[len++] = *in; - in++; - } - number[len++] = '\0'; - Expr *e = AllocExpr(); - e->op = CONSTANT; - e->v = atof(number); - Unparsed[UnparsedCnt++] = e; - } else if(isalpha(c) || c == '_') { - char name[70]; - int len = 0; - while(isforname(*in) && len < 30) { - name[len++] = *in; - in++; + case TokenType::UNARY_OP: { + Expr *e = a.expr; + switch(op.expr->op) { + case Expr::Op::NEGATE: e = e->Negate(); break; + case Expr::Op::SQRT: e = e->Sqrt(); break; + case Expr::Op::SQUARE: e = e->Times(e); break; + case Expr::Op::SIN: e = e->Times(Expr::From(PI/180))->Sin(); break; + case Expr::Op::COS: e = e->Times(Expr::From(PI/180))->Cos(); break; + case Expr::Op::ASIN: e = e->ASin()->Times(Expr::From(180/PI)); break; + case Expr::Op::ACOS: e = e->ACos()->Times(Expr::From(180/PI)); break; + default: ssassert(false, "Unexpected unary operator"); } - name[len++] = '\0'; - - Expr *e = AllocExpr(); - if(strcmp(name, "sqrt")==0) { - e->op = UNARY_OP; - e->c = 'q'; - } else if(strcmp(name, "cos")==0) { - e->op = UNARY_OP; - e->c = 'c'; - } else if(strcmp(name, "sin")==0) { - e->op = UNARY_OP; - e->c = 's'; - } else if(strcmp(name, "pi")==0) { - e->op = CONSTANT; - e->v = PI; - } else { - throw "unknown name"; + r.expr = e; + break; + } + + default: ssassert(false, "Unexpected operator"); + } + stack.push_back(r); + + return true; +} + +bool ExprParser::Parse(std::string *error, size_t reduceUntil) { + while(true) { + Token t = Lex(error); + switch(t.type) { + case TokenType::ERROR: + return false; + + case TokenType::END: + case TokenType::PAREN_RIGHT: + while(stack.size() > 1 + reduceUntil) { + if(!Reduce(error)) return false; + } + + if(t.type == TokenType::PAREN_RIGHT) { + stack.push_back(t); + } + return true; + + case TokenType::PAREN_LEFT: { + // sub-expression + if(!Parse(error, /*reduceUntil=*/stack.size())) return false; + + if(stack.empty() || stack.back().type != TokenType::PAREN_RIGHT) { + *error = "Expected ')'"; + return false; + } + stack.pop_back(); + break; } - Unparsed[UnparsedCnt++] = e; - } else if(strchr("+-*/()", c)) { - Expr *e = AllocExpr(); - e->op = (c == '(' || c == ')') ? PAREN : BINARY_OP; - e->c = c; - Unparsed[UnparsedCnt++] = e; - in++; - } else if(isspace(c)) { - // Ignore whitespace - in++; - } else { - // This is a lex error. - throw "unexpected characters"; + + case TokenType::BINARY_OP: + if((stack.size() > reduceUntil && stack.back().type != TokenType::OPERAND) || + stack.size() == reduceUntil) { + if(t.expr->op == Expr::Op::MINUS) { + t.type = TokenType::UNARY_OP; + t.expr->op = Expr::Op::NEGATE; + stack.push_back(t); + break; + } + } + + while(stack.size() > 1 + reduceUntil && + Precedence(t) <= Precedence(stack[stack.size() - 2])) { + if(!Reduce(error)) return false; + } + + stack.push_back(t); + break; + + case TokenType::UNARY_OP: + case TokenType::OPERAND: + stack.push_back(t); + break; } } + + return true; } -Expr *Expr::From(const char *in, bool popUpError) { - UnparsedCnt = 0; - UnparsedP = 0; - OperandsP = 0; - OperatorsP = 0; +Expr *ExprParser::Parse(const std::string &input, std::string *error) { + ExprParser parser; + parser.it = input.cbegin(); + parser.end = input.cend(); + if(!parser.Parse(error)) return NULL; - Expr *r; - try { - Lex(in); - Parse(); - r = PopOperand(); - } catch (const char *e) { - dbp("exception: parse/lex error: %s", e); + Token r = parser.PopOperand(error); + if(r.IsError()) return NULL; + return r.expr; +} + +Expr *Expr::Parse(const std::string &input, std::string *error) { + return ExprParser::Parse(input, error); +} + +Expr *Expr::From(const std::string &input, bool popUpError) { + std::string error; + Expr *e = ExprParser::Parse(input, &error); + if(!e) { + dbp("Parse/lex error: %s", error.c_str()); if(popUpError) { - Error("Not a valid number or expression: '%s'", in); + Error("Not a valid number or expression: '%s'.\n%s.", + input.c_str(), error.c_str()); } - return NULL; } - return r; + return e; } - diff --git a/src/expr.h b/src/expr.h index eaf79b0..7109cf6 100644 --- a/src/expr.h +++ b/src/expr.h @@ -4,28 +4,29 @@ // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- - -#ifndef __EXPR_H -#define __EXPR_H - -class Expr; +#ifndef SOLVESPACE_EXPR_H +#define SOLVESPACE_EXPR_H class Expr { public: - enum { + enum class Op : uint32_t { // A parameter, by the hParam handle PARAM = 0, // A parameter, by a pointer straight in to the param table (faster, // if we know that the param table won't move around) PARAM_PTR = 1, + // Operands CONSTANT = 20, + VARIABLE = 21, + // Binary ops PLUS = 100, MINUS = 101, TIMES = 102, DIV = 103, + // Unary ops NEGATE = 104, SQRT = 105, SQUARE = 106, @@ -33,98 +34,70 @@ public: COS = 108, ASIN = 109, ACOS = 110, - - // Special helpers for when we're parsing an expression from text. - // Initially, literals (like a constant number) appear in the same - // format as they will in the finished expression, but the operators - // are different until the parser fixes things up (and builds the - // tree from the flat list that the lexer outputs). - ALL_RESOLVED = 1000, - PAREN = 1001, - BINARY_OP = 1002, - UNARY_OP = 1003 }; - int op; + Op op; Expr *a; union { double v; hParam parh; Param *parp; Expr *b; - - // For use while parsing - char c; }; - Expr() { } - Expr(double val) : op(CONSTANT) { v = val; } + Expr() = default; + Expr(double val) : op(Op::CONSTANT) { v = val; } - static inline Expr *AllocExpr(void) + static inline Expr *AllocExpr() { return (Expr *)AllocTemporary(sizeof(Expr)); } static Expr *From(hParam p); static Expr *From(double v); - Expr *AnyOp(int op, Expr *b); - inline Expr *Plus (Expr *b_) { return AnyOp(PLUS, b_); } - inline Expr *Minus(Expr *b_) { return AnyOp(MINUS, b_); } - inline Expr *Times(Expr *b_) { return AnyOp(TIMES, b_); } - inline Expr *Div (Expr *b_) { return AnyOp(DIV, b_); } - - inline Expr *Negate(void) { return AnyOp(NEGATE, NULL); } - inline Expr *Sqrt (void) { return AnyOp(SQRT, NULL); } - inline Expr *Square(void) { return AnyOp(SQUARE, NULL); } - inline Expr *Sin (void) { return AnyOp(SIN, NULL); } - inline Expr *Cos (void) { return AnyOp(COS, NULL); } - inline Expr *ASin (void) { return AnyOp(ASIN, NULL); } - inline Expr *ACos (void) { return AnyOp(ACOS, NULL); } - - Expr *PartialWrt(hParam p); - double Eval(void); - uint64_t ParamsUsed(void); - bool DependsOn(hParam p); + Expr *AnyOp(Op op, Expr *b); + inline Expr *Plus (Expr *b_) { return AnyOp(Op::PLUS, b_); } + inline Expr *Minus(Expr *b_) { return AnyOp(Op::MINUS, b_); } + inline Expr *Times(Expr *b_) { return AnyOp(Op::TIMES, b_); } + inline Expr *Div (Expr *b_) { return AnyOp(Op::DIV, b_); } + + inline Expr *Negate() { return AnyOp(Op::NEGATE, NULL); } + inline Expr *Sqrt () { return AnyOp(Op::SQRT, NULL); } + inline Expr *Square() { return AnyOp(Op::SQUARE, NULL); } + inline Expr *Sin () { return AnyOp(Op::SIN, NULL); } + inline Expr *Cos () { return AnyOp(Op::COS, NULL); } + inline Expr *ASin () { return AnyOp(Op::ASIN, NULL); } + inline Expr *ACos () { return AnyOp(Op::ACOS, NULL); } + + Expr *PartialWrt(hParam p) const; + double Eval() const; + uint64_t ParamsUsed() const; + bool DependsOn(hParam p) const; static bool Tol(double a, double b); - Expr *FoldConstants(void); + Expr *FoldConstants(); void Substitute(hParam oldh, hParam newh); static const hParam NO_PARAMS, MULTIPLE_PARAMS; - hParam ReferencedParams(ParamList *pl); + hParam ReferencedParams(ParamList *pl) const; - void ParamsToPointers(void); + void ParamsToPointers(); - std::string Print(void); + std::string Print() const; // number of child nodes: 0 (e.g. constant), 1 (sqrt), or 2 (+) - int Children(void); + int Children() const; // total number of nodes in the tree - int Nodes(void); + int Nodes() const; // Make a simple copy - Expr *DeepCopy(void); + Expr *DeepCopy() const; // Make a copy, with the parameters (usually referenced by hParam) // resolved to pointers to the actual value. This speeds things up // considerably. Expr *DeepCopyWithParamsAsPointers(IdList *firstTry, - IdList *thenTry); - - static Expr *From(const char *in, bool popUpError); - static void Lex(const char *in); - static Expr *Next(void); - static void Consume(void); + IdList *thenTry) const; - static void PushOperator(Expr *e); - static Expr *PopOperator(void); - static Expr *TopOperator(void); - static void PushOperand(Expr *e); - static Expr *PopOperand(void); - - static void Reduce(void); - static void ReduceAndPush(Expr *e); - static int Precedence(Expr *e); - - static int Precedence(int op); - static void Parse(void); + static Expr *Parse(const std::string &input, std::string *error); + static Expr *From(const std::string &input, bool popUpError); }; class ExprVector { @@ -136,15 +109,15 @@ public: static ExprVector From(hParam x, hParam y, hParam z); static ExprVector From(double x, double y, double z); - ExprVector Plus(ExprVector b); - ExprVector Minus(ExprVector b); - Expr *Dot(ExprVector b); - ExprVector Cross(ExprVector b); - ExprVector ScaledBy(Expr *s); - ExprVector WithMagnitude(Expr *s); - Expr *Magnitude(void); + ExprVector Plus(ExprVector b) const; + ExprVector Minus(ExprVector b) const; + Expr *Dot(ExprVector b) const; + ExprVector Cross(ExprVector b) const; + ExprVector ScaledBy(Expr *s) const; + ExprVector WithMagnitude(Expr *s) const; + Expr *Magnitude() const; - Vector Eval(void); + Vector Eval() const; }; class ExprQuaternion { @@ -155,15 +128,13 @@ public: static ExprQuaternion From(Quaternion qn); static ExprQuaternion From(hParam w, hParam vx, hParam vy, hParam vz); - ExprVector RotationU(void); - ExprVector RotationV(void); - ExprVector RotationN(void); + ExprVector RotationU() const; + ExprVector RotationV() const; + ExprVector RotationN() const; - ExprVector Rotate(ExprVector p); - ExprQuaternion Times(ExprQuaternion b); + ExprVector Rotate(ExprVector p) const; + ExprQuaternion Times(ExprQuaternion b) const; - Expr *Magnitude(void); + Expr *Magnitude() const; }; - #endif - diff --git a/src/file.cpp b/src/file.cpp index daad828..acb931f 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -16,12 +16,12 @@ static int StrStartsWith(const char *str, const char *start) { // sketch. This does not leave the program in an acceptable state (with the // references created, and so on), so anyone calling this must fix that later. //----------------------------------------------------------------------------- -void SolveSpaceUI::ClearExisting(void) { +void SolveSpaceUI::ClearExisting() { UndoClearStack(&redo); UndoClearStack(&undo); - for(int i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); + for(hGroup hg : SK.groupOrder) { + Group *g = SK.GetGroup(hg); g->Clear(); } @@ -33,16 +33,17 @@ void SolveSpaceUI::ClearExisting(void) { SK.entity.Clear(); SK.param.Clear(); + images.clear(); } -hGroup SolveSpaceUI::CreateDefaultDrawingGroup(void) { +hGroup SolveSpaceUI::CreateDefaultDrawingGroup() { Group g = {}; // And an empty group, for the first stuff the user draws. g.visible = true; - g.name = "sketch-in-plane"; - g.type = Group::DRAWING_WORKPLANE; - g.subtype = Group::WORKPLANE_BY_POINT_ORTHO; + g.name = C_("group-name", "sketch-in-plane"); + g.type = Group::Type::DRAWING_WORKPLANE; + g.subtype = Group::Subtype::WORKPLANE_BY_POINT_ORTHO; g.order = 1; g.predef.q = Quaternion::From(1, 0, 0, 0); hRequest hr = Request::HREQUEST_REFERENCE_XY; @@ -52,14 +53,14 @@ hGroup SolveSpaceUI::CreateDefaultDrawingGroup(void) { return g.h; } -void SolveSpaceUI::NewFile(void) { +void SolveSpaceUI::NewFile() { ClearExisting(); // Our initial group, that contains the references. Group g = {}; g.visible = true; - g.name = "#references"; - g.type = Group::DRAWING_3D; + g.name = C_("group-name", "#references"); + g.type = Group::Type::DRAWING_3D; g.order = 0; g.h = Group::HGROUP_REFERENCES; SK.group.Add(&g); @@ -67,7 +68,7 @@ void SolveSpaceUI::NewFile(void) { // Let's create three two-d coordinate systems, for the coordinate // planes; these are our references, present in every sketch. Request r = {}; - r.type = Request::WORKPLANE; + r.type = Request::Type::WORKPLANE; r.group = Group::HGROUP_REFERENCES; r.workplane = Entity::FREE_IN_3D; @@ -116,8 +117,8 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = { { 'g', "Group.allDimsReference", 'b', &(SS.sv.g.allDimsReference) }, { 'g', "Group.scale", 'f', &(SS.sv.g.scale) }, { 'g', "Group.remap", 'M', &(SS.sv.g.remap) }, - { 'g', "Group.impFile", 'S', &(SS.sv.g.linkFile) }, - { 'g', "Group.impFileRel", 'S', &(SS.sv.g.linkFileRel) }, + { 'g', "Group.impFile", 'i', NULL }, + { 'g', "Group.impFileRel", 'P', &(SS.sv.g.linkFile) }, { 'p', "Param.h.v.", 'x', &(SS.sv.p.h.v) }, { 'p', "Param.val", 'f', &(SS.sv.p.val) }, @@ -131,6 +132,8 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = { { 'r', "Request.style", 'x', &(SS.sv.r.style) }, { 'r', "Request.str", 'S', &(SS.sv.r.str) }, { 'r', "Request.font", 'S', &(SS.sv.r.font) }, + { 'r', "Request.file", 'P', &(SS.sv.r.file) }, + { 'r', "Request.aspectRatio", 'f', &(SS.sv.r.aspectRatio) }, { 'e', "Entity.h.v", 'x', &(SS.sv.e.h.v) }, { 'e', "Entity.type", 'd', &(SS.sv.e.type) }, @@ -138,6 +141,7 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = { { 'e', "Entity.style", 'x', &(SS.sv.e.style) }, { 'e', "Entity.str", 'S', &(SS.sv.e.str) }, { 'e', "Entity.font", 'S', &(SS.sv.e.font) }, + { 'e', "Entity.file", 'P', &(SS.sv.e.file) }, { 'e', "Entity.point[0].v", 'x', &(SS.sv.e.point[0].v) }, { 'e', "Entity.point[1].v", 'x', &(SS.sv.e.point[1].v) }, { 'e', "Entity.point[2].v", 'x', &(SS.sv.e.point[2].v) }, @@ -170,6 +174,7 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = { { 'c', "Constraint.group.v", 'x', &(SS.sv.c.group.v) }, { 'c', "Constraint.workplane.v", 'x', &(SS.sv.c.workplane.v) }, { 'c', "Constraint.valA", 'f', &(SS.sv.c.valA) }, + { 'c', "Constraint.valP.v", 'x', &(SS.sv.c.valP.v) }, { 'c', "Constraint.ptA.v", 'x', &(SS.sv.c.ptA.v) }, { 'c', "Constraint.ptB.v", 'x', &(SS.sv.c.ptB.v) }, { 'c', "Constraint.entityA.v", 'x', &(SS.sv.c.entityA.v) }, @@ -205,8 +210,9 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = { }; struct SAVEDptr { - IdList &M() { return *((IdList *)this); } - std::string &S() { return *((std::string *)this); } + EntityMap &M() { return *((EntityMap *)this); } + std::string &S() { return *((std::string *)this); } + Platform::Path &P() { return *((Platform::Path *)this); } bool &b() { return *((bool *)this); } RgbaColor &c() { return *((RgbaColor *)this); } int &d() { return *((int *)this); } @@ -214,7 +220,7 @@ struct SAVEDptr { uint32_t &x() { return *((uint32_t *)this); } }; -void SolveSpaceUI::SaveUsingTable(int type) { +void SolveSpaceUI::SaveUsingTable(const Platform::Path &filename, int type) { int i; for(i = 0; SAVED[i].type != 0; i++) { if(SAVED[i].type != type) continue; @@ -223,9 +229,11 @@ void SolveSpaceUI::SaveUsingTable(int type) { SAVEDptr *p = (SAVEDptr *)SAVED[i].ptr; // Any items that aren't specified are assumed to be zero if(fmt == 'S' && p->S().empty()) continue; + if(fmt == 'P' && p->P().IsEmpty()) continue; if(fmt == 'd' && p->d() == 0) continue; if(fmt == 'f' && EXACT(p->f() == 0.0)) continue; if(fmt == 'x' && p->x() == 0) continue; + if(fmt == 'i') continue; fprintf(fh, "%s=", SAVED[i].desc); switch(fmt) { @@ -236,76 +244,98 @@ void SolveSpaceUI::SaveUsingTable(int type) { case 'f': fprintf(fh, "%.20f", p->f()); break; case 'x': fprintf(fh, "%08x", p->x()); break; + case 'P': { + if(!p->P().IsEmpty()) { + Platform::Path relativePath = p->P().RelativeTo(filename.Parent()); + ssassert(!relativePath.IsEmpty(), "Cannot relativize path"); + fprintf(fh, "%s", relativePath.ToPortable().c_str()); + } + break; + } + case 'M': { - int j; fprintf(fh, "{\n"); - for(j = 0; j < p->M().n; j++) { - EntityMap *em = &(p->M().elem[j]); + // Sort the mapping, since EntityMap is not deterministic. + std::vector> sorted(p->M().begin(), p->M().end()); + std::sort(sorted.begin(), sorted.end(), + [](std::pair &a, std::pair &b) { + return a.second.v < b.second.v; + }); + for(auto it : sorted) { fprintf(fh, " %d %08x %d\n", - em->h.v, em->input.v, em->copyNumber); + it.second.v, it.first.input.v, it.first.copyNumber); } fprintf(fh, "}"); break; } - default: oops(); + case 'i': break; + + default: ssassert(false, "Unexpected value format"); } fprintf(fh, "\n"); } } -bool SolveSpaceUI::SaveToFile(const std::string &filename) { - // Make sure all the entities are regenerated up to date, since they - // will be exported. We reload the linked files because that rewrites - // the linkFileRel for our possibly-new filename. +bool SolveSpaceUI::SaveToFile(const Platform::Path &filename) { + // Make sure all the entities are regenerated up to date, since they will be exported. SS.ScheduleShowTW(); - SS.ReloadAllImported(); - SS.GenerateAll(SolveSpaceUI::GENERATE_ALL); + SS.GenerateAll(SolveSpaceUI::Generate::ALL); + + for(Group &g : SK.group) { + if(g.type != Group::Type::LINKED) continue; + + if(g.linkFile.RelativeTo(filename).IsEmpty()) { + Error("This sketch links the sketch '%s'; it can only be saved " + "on the same volume.", g.linkFile.raw.c_str()); + return false; + } + } - fh = ssfopen(filename, "wb"); + fh = OpenFile(filename, "wb"); if(!fh) { - Error("Couldn't write to file '%s'", filename.c_str()); + Error("Couldn't write to file '%s'", filename.raw.c_str()); return false; } fprintf(fh, "%s\n\n\n", VERSION_STRING); int i, j; - for(i = 0; i < SK.group.n; i++) { - sv.g = SK.group.elem[i]; - SaveUsingTable('g'); + for(auto &g : SK.group) { + sv.g = g; + SaveUsingTable(filename, 'g'); fprintf(fh, "AddGroup\n\n"); } - for(i = 0; i < SK.param.n; i++) { - sv.p = SK.param.elem[i]; - SaveUsingTable('p'); + for(auto &p : SK.param) { + sv.p = p; + SaveUsingTable(filename, 'p'); fprintf(fh, "AddParam\n\n"); } - for(i = 0; i < SK.request.n; i++) { - sv.r = SK.request.elem[i]; - SaveUsingTable('r'); + for(auto &r : SK.request) { + sv.r = r; + SaveUsingTable(filename, 'r'); fprintf(fh, "AddRequest\n\n"); } - for(i = 0; i < SK.entity.n; i++) { - (SK.entity.elem[i]).CalculateNumerical(true); - sv.e = SK.entity.elem[i]; - SaveUsingTable('e'); + for(auto &e : SK.entity) { + e.CalculateNumerical(/*forExport=*/true); + sv.e = e; + SaveUsingTable(filename, 'e'); fprintf(fh, "AddEntity\n\n"); } - for(i = 0; i < SK.constraint.n; i++) { - sv.c = SK.constraint.elem[i]; - SaveUsingTable('c'); + for(auto &c : SK.constraint) { + sv.c = c; + SaveUsingTable(filename, 'c'); fprintf(fh, "AddConstraint\n\n"); } - for(i = 0; i < SK.style.n; i++) { - sv.s = SK.style.elem[i]; + for(auto &s : SK.style) { + sv.s = s; if(sv.s.h.v >= Style::FIRST_CUSTOM) { - SaveUsingTable('s'); + SaveUsingTable(filename, 's'); fprintf(fh, "AddStyle\n\n"); } } @@ -313,10 +343,10 @@ bool SolveSpaceUI::SaveToFile(const std::string &filename) { // A group will have either a mesh or a shell, but not both; but the code // to print either of those just does nothing if the mesh/shell is empty. - Group *g = SK.GetGroup(SK.groupOrder.elem[SK.groupOrder.n - 1]); + Group *g = SK.GetGroup(*SK.groupOrder.Last()); SMesh *m = &g->runningMesh; for(i = 0; i < m->l.n; i++) { - STriangle *tr = &(m->l.elem[i]); + STriangle *tr = &(m->l[i]); fprintf(fh, "Triangle %08x %08x " "%.20f %.20f %.20f %.20f %.20f %.20f %.20f %.20f %.20f\n", tr->meta.face, tr->meta.color.ToPackedInt(), @@ -371,7 +401,7 @@ bool SolveSpaceUI::SaveToFile(const std::string &filename) { return true; } -void SolveSpaceUI::LoadUsingTable(char *key, char *val) { +void SolveSpaceUI::LoadUsingTable(const Platform::Path &filename, char *key, char *val) { int i; for(i = 0; SAVED[i].type != 0; i++) { if(strcmp(SAVED[i].desc, key)==0) { @@ -384,30 +414,40 @@ void SolveSpaceUI::LoadUsingTable(char *key, char *val) { case 'f': p->f() = atof(val); break; case 'x': sscanf(val, "%x", &u); p->x()= u; break; + case 'P': { + Platform::Path path = Platform::Path::FromPortable(val); + if(!path.IsEmpty()) { + p->P() = filename.Parent().Join(path).Expand(); + } + break; + } + case 'c': sscanf(val, "%x", &u); p->c() = RgbaColor::FromPackedInt(u); break; - case 'P': - p->S() = val; - break; - case 'M': { - // Don't clear this list! When the group gets added, it - // makes a shallow copy, so that would result in us - // freeing memory that we want to keep around. Just - // zero it out so that new memory is allocated. - p->M() = {}; + p->M().clear(); for(;;) { - EntityMap em; + EntityKey ek; + EntityId ei; char line2[1024]; if (fgets(line2, (int)sizeof(line2), fh) == NULL) break; - if(sscanf(line2, "%d %x %d", &(em.h.v), &(em.input.v), - &(em.copyNumber)) == 3) - { - p->M().Add(&em); + if(sscanf(line2, "%d %x %d", &(ei.v), &(ek.input.v), + &(ek.copyNumber)) == 3) { + if(ei.v == Entity::NO_ENTITY.v) { + // Commit bd84bc1a mistakenly introduced code that would remap + // some entities to NO_ENTITY. This was fixed in commit bd84bc1a, + // but files created meanwhile are corrupt, and can cause crashes. + // + // To fix this, we skip any such remaps when loading; they will be + // recreated on the next regeneration. Any resulting orphans will + // be pruned in the usual way, recovering to a well-defined state. + continue; + } + p->M().insert({ ek, ei }); } else { break; } @@ -415,7 +455,9 @@ void SolveSpaceUI::LoadUsingTable(char *key, char *val) { break; } - default: oops(); + case 'i': break; + + default: ssassert(false, "Unexpected value format"); } break; } @@ -425,13 +467,13 @@ void SolveSpaceUI::LoadUsingTable(char *key, char *val) { } } -bool SolveSpaceUI::LoadFromFile(const std::string &filename) { +bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel) { allConsistent = false; fileLoadError = false; - fh = ssfopen(filename, "rb"); + fh = OpenFile(filename, "rb"); if(!fh) { - Error("Couldn't read from file '%s'", filename.c_str()); + Error("Couldn't read from file '%s'", filename.raw.c_str()); return false; } @@ -456,11 +498,11 @@ bool SolveSpaceUI::LoadFromFile(const std::string &filename) { if(e) { *e = '\0'; char *key = line, *val = e+1; - LoadUsingTable(key, val); + LoadUsingTable(filename, key, val); } else if(strcmp(line, "AddGroup")==0) { // legacy files have a spurious dependency between linked groups // and their parent groups, remove - if(sv.g.type == Group::LINKED) + if(sv.g.type == Group::Type::LINKED) sv.g.opA.v = 0; SK.group.Add(&(sv.g)); @@ -504,24 +546,177 @@ bool SolveSpaceUI::LoadFromFile(const std::string &filename) { fclose(fh); if(fileLoadError) { - Error("Unrecognized data in file. This file may be corrupt, or " - "from a new version of the program."); + Error(_("Unrecognized data in file. This file may be corrupt, or " + "from a newer version of the program.")); // At least leave the program in a non-crashing state. - if(SK.group.n == 0) { + if(SK.group.IsEmpty()) { NewFile(); } } + if(!ReloadAllLinked(filename, canCancel)) { + return false; + } + UpgradeLegacyData(); return true; } -bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList *le, +void SolveSpaceUI::UpgradeLegacyData() { + for(Request &r : SK.request) { + switch(r.type) { + // TTF text requests saved in versions prior to 3.0 only have two + // reference points (origin and origin plus v); version 3.0 adds two + // more points, and if we don't do anything, then they will appear + // at workplane origin, and the solver will mess up the sketch if + // it is not fully constrained. + case Request::Type::TTF_TEXT: { + IdList entity = {}; + IdList param = {}; + r.Generate(&entity, ¶m); + + // If we didn't load all of the entities and params that this + // request would generate, then add them now, so that we can + // force them to their appropriate positions. + for(Param &p : param) { + if(SK.param.FindByIdNoOops(p.h) != NULL) continue; + SK.param.Add(&p); + } + bool allPointsExist = true; + for(Entity &e : entity) { + if(SK.entity.FindByIdNoOops(e.h) != NULL) continue; + SK.entity.Add(&e); + allPointsExist = false; + } + + if(!allPointsExist) { + Entity *text = entity.FindById(r.h.entity(0)); + Entity *b = entity.FindById(text->point[2]); + Entity *c = entity.FindById(text->point[3]); + ExprVector bex, cex; + text->RectGetPointsExprs(&bex, &cex); + b->PointForceParamTo(bex.Eval()); + c->PointForceParamTo(cex.Eval()); + } + entity.Clear(); + param.Clear(); + break; + } + + default: + break; + } + } + + // Constraints saved in versions prior to 3.0 never had any params; + // version 3.0 introduced params to constraints to avoid the hairy ball problem, + // so force them where they belong. + IdList oldParam = {}; + SK.param.DeepCopyInto(&oldParam); + SS.GenerateAll(SolveSpaceUI::Generate::REGEN); + + auto AllParamsExistFor = [&](Constraint &c) { + IdList param = {}; + c.Generate(¶m); + bool allParamsExist = true; + for(Param &p : param) { + if(oldParam.FindByIdNoOops(p.h) != NULL) continue; + allParamsExist = false; + break; + } + param.Clear(); + return allParamsExist; + }; + + for(Constraint &c : SK.constraint) { + switch(c.type) { + case Constraint::Type::PT_ON_LINE: { + if(AllParamsExistFor(c)) continue; + + EntityBase *eln = SK.GetEntity(c.entityA); + EntityBase *ea = SK.GetEntity(eln->point[0]); + EntityBase *eb = SK.GetEntity(eln->point[1]); + EntityBase *ep = SK.GetEntity(c.ptA); + + ExprVector exp = ep->PointGetExprsInWorkplane(c.workplane); + ExprVector exa = ea->PointGetExprsInWorkplane(c.workplane); + ExprVector exb = eb->PointGetExprsInWorkplane(c.workplane); + ExprVector exba = exb.Minus(exa); + Param *p = SK.GetParam(c.h.param(0)); + p->val = exba.Dot(exp.Minus(exa))->Eval() / exba.Dot(exba)->Eval(); + break; + } + + case Constraint::Type::CUBIC_LINE_TANGENT: { + if(AllParamsExistFor(c)) continue; + + EntityBase *cubic = SK.GetEntity(c.entityA); + EntityBase *line = SK.GetEntity(c.entityB); + + ExprVector a; + if(c.other) { + a = cubic->CubicGetFinishTangentExprs(); + } else { + a = cubic->CubicGetStartTangentExprs(); + } + + ExprVector b = line->VectorGetExprs(); + + Param *param = SK.GetParam(c.h.param(0)); + param->val = a.Dot(b)->Eval() / b.Dot(b)->Eval(); + break; + } + + case Constraint::Type::SAME_ORIENTATION: { + if(AllParamsExistFor(c)) continue; + + EntityBase *an = SK.GetEntity(c.entityA); + EntityBase *bn = SK.GetEntity(c.entityB); + + ExprVector a = an->NormalExprsN(); + ExprVector b = bn->NormalExprsN(); + + Param *param = SK.GetParam(c.h.param(0)); + param->val = a.Dot(b)->Eval() / b.Dot(b)->Eval(); + break; + } + + case Constraint::Type::PARALLEL: { + if(AllParamsExistFor(c)) continue; + + EntityBase *ea = SK.GetEntity(c.entityA), + *eb = SK.GetEntity(c.entityB); + ExprVector a = ea->VectorGetExprsInWorkplane(c.workplane); + ExprVector b = eb->VectorGetExprsInWorkplane(c.workplane); + + Param *param = SK.GetParam(c.h.param(0)); + param->val = a.Dot(b)->Eval() / b.Dot(b)->Eval(); + break; + } + + default: + break; + } + } + oldParam.Clear(); +} + +bool SolveSpaceUI::LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le, + SMesh *m, SShell *sh) +{ + if(strcmp(filename.Extension().c_str(), "emn")==0) { + return LinkIDF(filename, le, m, sh); + } else { + return LoadEntitiesFromSlvs(filename, le, m, sh); + } +} + +bool SolveSpaceUI::LoadEntitiesFromSlvs(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh) { SSurface srf = {}; SCurve crv = {}; - fh = ssfopen(filename, "rb"); + fh = OpenFile(filename, "rb"); if(!fh) return false; le->Clear(); @@ -542,11 +737,10 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList if(e) { *e = '\0'; char *key = line, *val = e+1; - LoadUsingTable(key, val); + LoadUsingTable(filename, key, val); } else if(strcmp(line, "AddGroup")==0) { - // Don't leak memory; these get allocated whether we want them - // or not. - sv.g.remap.Clear(); + // These get allocated whether we want them or not. + sv.g.remap.clear(); } else if(strcmp(line, "AddParam")==0) { } else if(strcmp(line, "AddEntity")==0) { @@ -557,7 +751,13 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList } else if(strcmp(line, "AddConstraint")==0) { } else if(strcmp(line, "AddStyle")==0) { - + // Linked file contains a style that we don't have yet, + // so import it. + if (SK.style.FindByIdNoOops(sv.s.h) == nullptr) { + SK.style.Add(&(sv.s)); + } + sv.s = {}; + Style::FillDefaultStyle(&sv.s); } else if(strcmp(line, VERSION_STRING)==0) { } else if(StrStartsWith(line, "Triangle ")) { @@ -569,7 +769,7 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList &(tr.a.x), &(tr.a.y), &(tr.a.z), &(tr.b.x), &(tr.b.y), &(tr.b.z), &(tr.c.x), &(tr.c.y), &(tr.c.z)) != 11) { - oops(); + ssassert(false, "Unexpected Triangle format"); } tr.meta.color = RgbaColor::FromPackedInt((uint32_t)rgba); m->AddTriangle(&tr); @@ -578,7 +778,7 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList if(sscanf(line, "Surface %x %x %x %d %d", &(srf.h.v), &rgba, &(srf.face), &(srf.degm), &(srf.degn)) != 5) { - oops(); + ssassert(false, "Unexpected Surface format"); } srf.color = RgbaColor::FromPackedInt((uint32_t)rgba); } else if(StrStartsWith(line, "SCtrl ")) { @@ -588,7 +788,7 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList if(sscanf(line, "SCtrl %d %d %lf %lf %lf Weight %lf", &i, &j, &(c.x), &(c.y), &(c.z), &w) != 6) { - oops(); + ssassert(false, "Unexpected SCtrl format"); } srf.ctrl[i][j] = c; srf.weight[i][j] = w; @@ -600,7 +800,7 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList &(stb.start.x), &(stb.start.y), &(stb.start.z), &(stb.finish.x), &(stb.finish.y), &(stb.finish.z)) != 8) { - oops(); + ssassert(false, "Unexpected TrimBy format"); } stb.backwards = (backwards != 0); srf.trim.Add(&stb); @@ -615,7 +815,7 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList &(crv.exact.deg), &(crv.surfA.v), &(crv.surfB.v)) != 5) { - oops(); + ssassert(false, "Unexpected Curve format"); } crv.isExact = (isExact != 0); } else if(StrStartsWith(line, "CCtrl ")) { @@ -625,7 +825,7 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList if(sscanf(line, "CCtrl %d %lf %lf %lf Weight %lf", &i, &(c.x), &(c.y), &(c.z), &w) != 5) { - oops(); + ssassert(false, "Unexpected CCtrl format"); } crv.exact.ctrl[i] = c; crv.exact.weight[i] = w; @@ -636,234 +836,169 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList &vertex, &(scpt.p.x), &(scpt.p.y), &(scpt.p.z)) != 4) { - oops(); + ssassert(false, "Unexpected CurvePt format"); } scpt.vertex = (vertex != 0); crv.pts.Add(&scpt); } else if(strcmp(line, "AddCurve")==0) { sh->curve.Add(&crv); crv = {}; - } else { - oops(); - } + } else ssassert(false, "Unexpected operation"); } fclose(fh); return true; } -//----------------------------------------------------------------------------- -// Handling of the relative-absolute path transformations for links -//----------------------------------------------------------------------------- -static std::vector Split(const std::string &haystack, const std::string &needle) -{ - std::vector result; - - size_t oldpos = 0, pos = 0; - while(true) { - oldpos = pos; - pos = haystack.find(needle, pos); - if(pos == std::string::npos) break; - result.push_back(haystack.substr(oldpos, pos - oldpos)); - pos += needle.length(); +static Platform::MessageDialog::Response LocateImportedFile(const Platform::Path &filename, + bool canCancel) { + Platform::MessageDialogRef dialog = CreateMessageDialog(SS.GW.window); + + using Platform::MessageDialog; + dialog->SetType(MessageDialog::Type::QUESTION); + dialog->SetTitle(C_("title", "Missing File")); + dialog->SetMessage(ssprintf(C_("dialog", "The linked file “%s” is not present."), + filename.raw.c_str())); + dialog->SetDescription(C_("dialog", "Do you want to locate it manually?\n\n" + "If you decline, any geometry that depends on " + "the missing file will be permanently removed.")); + dialog->AddButton(C_("button", "&Yes"), MessageDialog::Response::YES, + /*isDefault=*/true); + dialog->AddButton(C_("button", "&No"), MessageDialog::Response::NO); + if(canCancel) { + dialog->AddButton(C_("button", "&Cancel"), MessageDialog::Response::CANCEL); } - if(oldpos != haystack.length() - 1) - result.push_back(haystack.substr(oldpos)); - - return result; + // FIXME(async): asyncify this call + return dialog->RunModal(); } -static std::string Join(const std::vector &parts, const std::string &separator) -{ - bool first = true; - std::string result; - for(auto &part : parts) { - if(!first) result += separator; - result += part; - first = false; - } - return result; -} +bool SolveSpaceUI::ReloadAllLinked(const Platform::Path &saveFile, bool canCancel) { + Platform::SettingsRef settings = Platform::GetSettings(); -static bool PlatformPathEqual(const std::string &a, const std::string &b) -{ - // Case-sensitivity is actually per-volume on both Windows and OS X, - // but it is extremely tedious to implement and test for little benefit. -#if defined(WIN32) - std::wstring wa = Widen(a), wb = Widen(b); - return std::equal(wa.begin(), wa.end(), wb.begin(), /*wb.end(),*/ - [](wchar_t wca, wchar_t wcb) { return towlower(wca) == towlower(wcb); }); -#elif defined(__APPLE__) - return !strcasecmp(a.c_str(), b.c_str()); -#else - return a == b; -#endif -} + std::map linkMap; -static std::string MakePathRelative(const std::string &base, const std::string &path) -{ - std::vector baseParts = Split(base, PATH_SEP), - pathParts = Split(path, PATH_SEP), - resultParts; - baseParts.pop_back(); - - size_t common; - for(common = 0; common < baseParts.size() && common < pathParts.size(); common++) { - if(!PlatformPathEqual(baseParts[common], pathParts[common])) - break; - } + allConsistent = false; - for(size_t i = common; i < baseParts.size(); i++) - resultParts.push_back(".."); + for(Group &g : SK.group) { + if(g.type != Group::Type::LINKED) continue; - resultParts.insert(resultParts.end(), - pathParts.begin() + common, pathParts.end()); + g.impEntity.Clear(); + g.impMesh.Clear(); + g.impShell.Clear(); - return Join(resultParts, PATH_SEP); -} + // If we prompted for this specific file before, don't ask again. + if(linkMap.count(g.linkFile)) { + g.linkFile = linkMap[g.linkFile]; + } -static std::string MakePathAbsolute(const std::string &base, const std::string &path) -{ - std::vector resultParts = Split(base, PATH_SEP), - pathParts = Split(path, PATH_SEP); - resultParts.pop_back(); - - for(auto &part : pathParts) { - if(part == ".") { - /* do nothing */ - } else if(part == "..") { - if(resultParts.empty()) oops(); - resultParts.pop_back(); +try_again: + if(LoadEntitiesFromFile(g.linkFile, &g.impEntity, &g.impMesh, &g.impShell)) { + // We loaded the data, good. Now import its dependencies as well. + for(Entity &e : g.impEntity) { + if(e.type != Entity::Type::IMAGE) continue; + if(!ReloadLinkedImage(g.linkFile, &e.file, canCancel)) { + return false; + } + } + } else if(linkMap.count(g.linkFile) == 0) { + dbp("Missing file for group: %s", g.name.c_str()); + // The file was moved; prompt the user for its new location. + switch(LocateImportedFile(g.linkFile.RelativeTo(saveFile), canCancel)) { + case Platform::MessageDialog::Response::YES: { + Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window); + dialog->AddFilters(Platform::SolveSpaceModelFileFilters); + dialog->ThawChoices(settings, "LinkSketch"); + if(dialog->RunModal()) { + dialog->FreezeChoices(settings, "LinkSketch"); + linkMap[g.linkFile] = dialog->GetFilename(); + g.linkFile = dialog->GetFilename(); + goto try_again; + } else { + if(canCancel) return false; + break; + } + } + + case Platform::MessageDialog::Response::NO: + linkMap[g.linkFile].Clear(); + // Geometry will be pruned by GenerateAll(). + break; + + case Platform::MessageDialog::Response::CANCEL: + return false; + + default: + ssassert(false, "Unexpected dialog response"); + } } else { - resultParts.push_back(part); + // User was already asked to and refused to locate a missing linked file. } } - return Join(resultParts, PATH_SEP); -} - -static void PathSepNormalize(std::string &filename) -{ - for(size_t i = 0; i < filename.length(); i++) { - if(filename[i] == '\\') - filename[i] = '/'; - } -} + for(Request &r : SK.request) { + if(r.type != Request::Type::IMAGE) continue; -static std::string PathSepPlatformToUNIX(const std::string &filename) -{ -#if defined(WIN32) - std::string result = filename; - for(size_t i = 0; i < result.length(); i++) { - if(result[i] == '\\') - result[i] = '/'; + if(!ReloadLinkedImage(saveFile, &r.file, canCancel)) { + return false; + } } - return result; -#else - return filename; -#endif -} -static std::string PathSepUNIXToPlatform(const std::string &filename) -{ -#if defined(WIN32) - std::string result = filename; - for(size_t i = 0; i < result.length(); i++) { - if(result[i] == '/') - result[i] = '\\'; - } - return result; -#else - return filename; -#endif + return true; } -bool SolveSpaceUI::ReloadAllImported(bool canCancel) -{ - std::map linkMap; - allConsistent = false; - - int i; - for(i = 0; i < SK.group.n; i++) { - Group *g = &(SK.group.elem[i]); - if(g->type != Group::LINKED) continue; - - if(isalpha(g->linkFile[0]) && g->linkFile[1] == ':') { - // Make sure that g->linkFileRel always contains a relative path - // in an UNIX format, even after we load an old file which had - // the path in Windows format - PathSepNormalize(g->linkFileRel); - } +bool SolveSpaceUI::ReloadLinkedImage(const Platform::Path &saveFile, + Platform::Path *filename, bool canCancel) { + Platform::SettingsRef settings = Platform::GetSettings(); + + std::shared_ptr pixmap; + bool promptOpenFile = false; + if(filename->IsEmpty()) { + // We're prompting the user for a new image. + promptOpenFile = true; + } else { + auto image = SS.images.find(*filename); + if(image != SS.images.end()) return true; + + pixmap = Pixmap::ReadPng(*filename); + if(pixmap == NULL) { + // The file was moved; prompt the user for its new location. + switch(LocateImportedFile(filename->RelativeTo(saveFile), canCancel)) { + case Platform::MessageDialog::Response::YES: + promptOpenFile = true; + break; - g->impEntity.Clear(); - g->impMesh.Clear(); - g->impShell.Clear(); + case Platform::MessageDialog::Response::NO: + // We don't know where the file is, record it as absent. + break; - if(linkMap.count(g->linkFile)) { - std::string newPath = linkMap[g->linkFile]; - if(!newPath.empty()) - g->linkFile = newPath; - } + case Platform::MessageDialog::Response::CANCEL: + return false; - // In a newly created group we only have an absolute path. - if(!g->linkFileRel.empty()) { - std::string rel = PathSepUNIXToPlatform(g->linkFileRel); - std::string fromRel = MakePathAbsolute(SS.saveFile, rel); - FILE *test = ssfopen(fromRel, "rb"); - if(test) { - fclose(test); - // Okay, exists; update the absolute path. - g->linkFile = fromRel; - } else { - // It doesn't exist. Perhaps the file was moved but the tree wasn't, and we - // can use the absolute filename to get us back. The relative path will be - // updated below. + default: + ssassert(false, "Unexpected dialog response"); } } + } -try_load_file: - if(LoadEntitiesFromFile(g->linkFile, &(g->impEntity), &(g->impMesh), &(g->impShell))) - { - if(!SS.saveFile.empty()) { - // Record the linked file's name relative to our filename; - // if the entire tree moves, then everything will still work - std::string rel = MakePathRelative(SS.saveFile, g->linkFile); - g->linkFileRel = PathSepPlatformToUNIX(rel); - } else { - // We're not yet saved, so can't make it absolute. - // This will only be used for display purposes, as SS.saveFile - // is always nonempty when we are actually writing anything. - g->linkFileRel = g->linkFile; - } - } else if(!linkMap.count(g->linkFile)) { - switch(LocateImportedFileYesNoCancel(g->linkFileRel, canCancel)) { - case DIALOG_YES: { - std::string oldImpFile = g->linkFile; - if(!GetOpenFile(&g->linkFile, "", SlvsFileFilter)) { - if(canCancel) - return false; - break; - } else { - linkMap[oldImpFile] = g->linkFile; - goto try_load_file; - } - } - - case DIALOG_NO: - linkMap[g->linkFile] = ""; - /* Geometry will be pruned by GenerateAll(). */ - break; - - case DIALOG_CANCEL: - return false; + if(promptOpenFile) { + Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window); + dialog->AddFilters(Platform::RasterFileFilters); + dialog->ThawChoices(settings, "LinkImage"); + if(dialog->RunModal()) { + dialog->FreezeChoices(settings, "LinkImage"); + *filename = dialog->GetFilename(); + pixmap = Pixmap::ReadPng(*filename); + if(pixmap == NULL) { + Error("The image '%s' is corrupted.", filename->raw.c_str()); } - } else { - // User was already asked to and refused to locate a missing - // linked file. + // We know where the file is now, good. + } else if(canCancel) { + return false; } } + // We loaded the data, good. + SS.images[*filename] = pixmap; return true; } - diff --git a/src/generate.cpp b/src/generate.cpp index 581add6..fdda994 100644 --- a/src/generate.cpp +++ b/src/generate.cpp @@ -13,39 +13,37 @@ void SolveSpaceUI::MarkGroupDirtyByEntity(hEntity he) { MarkGroupDirty(e->group); } -void SolveSpaceUI::MarkGroupDirty(hGroup hg) { - int i; +void SolveSpaceUI::MarkGroupDirty(hGroup hg, bool onlyThis) { bool go = false; - for(i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); - if(g->h.v == hg.v) { + for(auto const &gh : SK.groupOrder) { + Group *g = SK.GetGroup(gh); + if(g->h == hg) { go = true; } if(go) { g->clean = false; + if(onlyThis) break; } } unsaved = true; + ScheduleGenerateAll(); } -bool SolveSpaceUI::PruneOrphans(void) { - int i; - for(i = 0; i < SK.request.n; i++) { - Request *r = &(SK.request.elem[i]); - if(GroupExists(r->group)) continue; +bool SolveSpaceUI::PruneOrphans() { + auto r = std::find_if(SK.request.begin(), SK.request.end(), + [&](Request &r) { return !GroupExists(r.group); }); + if(r != SK.request.end()) { (deleted.requests)++; SK.request.RemoveById(r->h); return true; } - for(i = 0; i < SK.constraint.n; i++) { - Constraint *c = &(SK.constraint.elem[i]); - if(GroupExists(c->group)) continue; - + auto c = std::find_if(SK.constraint.begin(), SK.constraint.end(), + [&](Constraint &c) { return !GroupExists(c.group); }); + if(c != SK.constraint.end()) { (deleted.constraints)++; (deleted.nonTrivialConstraints)++; - SK.constraint.RemoveById(c->h); return true; } @@ -55,15 +53,10 @@ bool SolveSpaceUI::PruneOrphans(void) { bool SolveSpaceUI::GroupsInOrder(hGroup before, hGroup after) { if(before.v == 0) return true; if(after.v == 0) return true; - - int beforep = -1, afterp = -1; - int i; - for(i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); - if(g->h.v == before.v) beforep = i; - if(g->h.v == after.v) afterp = i; - } - if(beforep < 0 || afterp < 0) return false; + if(!GroupExists(before)) return false; + if(!GroupExists(after)) return false; + int beforep = SK.GetGroup(before)->order; + int afterp = SK.GetGroup(after)->order; if(beforep >= afterp) return false; return true; } @@ -75,7 +68,7 @@ bool SolveSpaceUI::GroupExists(hGroup hg) { bool SolveSpaceUI::EntityExists(hEntity he) { // A nonexstient entity is acceptable, though, usually just means it // doesn't apply. - if(he.v == Entity::NO_ENTITY.v) return true; + if(he == Entity::NO_ENTITY) return true; return SK.entity.FindByIdNoOops(he) ? true : false; } @@ -94,44 +87,38 @@ bool SolveSpaceUI::PruneGroups(hGroup hg) { } bool SolveSpaceUI::PruneRequests(hGroup hg) { - int i; - for(i = 0; i < SK.entity.n; i++) { - Entity *e = &(SK.entity.elem[i]); - if(e->group.v != hg.v) continue; - - if(EntityExists(e->workplane)) continue; - - if(!e->h.isFromRequest()) oops(); - + auto e = std::find_if(SK.entity.begin(), SK.entity.end(), + [&](Entity &e) { return e.group == hg && !EntityExists(e.workplane); }); + if(e != SK.entity.end()) { (deleted.requests)++; - SK.request.RemoveById(e->h.request()); + SK.entity.RemoveById(e->h); return true; } return false; } bool SolveSpaceUI::PruneConstraints(hGroup hg) { - int i; - for(i = 0; i < SK.constraint.n; i++) { - Constraint *c = &(SK.constraint.elem[i]); - if(c->group.v != hg.v) continue; - - if(EntityExists(c->workplane) && - EntityExists(c->ptA) && - EntityExists(c->ptB) && - EntityExists(c->entityA) && - EntityExists(c->entityB) && - EntityExists(c->entityC) && - EntityExists(c->entityD)) - { - continue; + auto c = std::find_if(SK.constraint.begin(), SK.constraint.end(), [&](Constraint &c) { + if(c.group != hg) + return false; + + if(EntityExists(c.workplane) && + EntityExists(c.ptA) && + EntityExists(c.ptB) && + EntityExists(c.entityA) && + EntityExists(c.entityB) && + EntityExists(c.entityC) && + EntityExists(c.entityD)) { + return false; } + return true; + }); + if(c != SK.constraint.end()) { (deleted.constraints)++; - if(c->type != Constraint::POINTS_COINCIDENT && - c->type != Constraint::HORIZONTAL && - c->type != Constraint::VERTICAL) - { + if(c->type != Constraint::Type::POINTS_COINCIDENT && + c->type != Constraint::Type::HORIZONTAL && + c->type != Constraint::Type::VERTICAL) { (deleted.nonTrivialConstraints)++; } @@ -141,30 +128,33 @@ bool SolveSpaceUI::PruneConstraints(hGroup hg) { return false; } -void SolveSpaceUI::GenerateAll(GenerateType type, bool andFindFree, bool genForBBox) { - int first, last, i, j; +void SolveSpaceUI::GenerateAll(Generate type, bool andFindFree, bool genForBBox) { + int first = 0, last = 0, i; + + uint64_t startMillis = GetMilliseconds(), + endMillis; SK.groupOrder.Clear(); - for(int i = 0; i < SK.group.n; i++) - SK.groupOrder.Add(&SK.group.elem[i].h); - std::sort(&SK.groupOrder.elem[0], &SK.groupOrder.elem[SK.groupOrder.n], + for(auto &g : SK.group) { SK.groupOrder.Add(&g.h); } + std::sort(SK.groupOrder.begin(), SK.groupOrder.end(), [](const hGroup &ha, const hGroup &hb) { return SK.GetGroup(ha)->order < SK.GetGroup(hb)->order; }); switch(type) { - case GENERATE_DIRTY: { + case Generate::DIRTY: { first = INT_MAX; last = 0; // Start from the first dirty group, and solve until the active group, // since all groups after the active group are hidden. + // Not using range-for because we're tracking the indices. for(i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); + Group *g = SK.GetGroup(SK.groupOrder[i]); if((!g->clean) || !g->IsSolvedOkay()) { first = min(first, i); } - if(g->h.v == SS.GW.activeGroup.v) { + if(g->h == SS.GW.activeGroup) { last = i; } } @@ -178,19 +168,19 @@ void SolveSpaceUI::GenerateAll(GenerateType type, bool andFindFree, bool genForB break; } - case GENERATE_ALL: + case Generate::ALL: first = 0; last = INT_MAX; break; - case GENERATE_REGEN: + case Generate::REGEN: first = -1; last = -1; break; - case GENERATE_UNTIL_ACTIVE: { + case Generate::UNTIL_ACTIVE: { for(i = 0; i < SK.groupOrder.n; i++) { - if(SK.groupOrder.elem[i].v == SS.GW.activeGroup.v) + if(SK.groupOrder[i] == SS.GW.activeGroup) break; } @@ -198,8 +188,6 @@ void SolveSpaceUI::GenerateAll(GenerateType type, bool andFindFree, bool genForB last = i; break; } - - default: oops(); } // If we're generating entities for display, first we need to find @@ -221,75 +209,44 @@ void SolveSpaceUI::GenerateAll(GenerateType type, bool andFindFree, bool genForB // Don't lose our numerical guesses when we regenerate. IdList prev = {}; SK.param.MoveSelfInto(&prev); + SK.param.ReserveMore(prev.n); + int oldEntityCount = SK.entity.n; SK.entity.Clear(); + SK.entity.ReserveMore(oldEntityCount); - int64_t inTime = GetMilliseconds(); - - bool displayedStatusMessage = false; + // Not using range-for because we're using the index inside the loop. for(i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); - - int64_t now = GetMilliseconds(); - // Display the status message if we've taken more than 400 ms, or - // if we've taken 200 ms but we're not even halfway done, or if - // we've already started displaying the status message. - if( (now - inTime > 400) || - ((now - inTime > 200) && i < (SK.groupOrder.n / 2)) || - displayedStatusMessage) - { - displayedStatusMessage = true; - std::string msg = ssprintf("generating group %d/%d", i, SK.groupOrder.n); - - int w, h; - GetGraphicsWindowSize(&w, &h); - glDrawBuffer(GL_FRONT); - glViewport(0, 0, w, h); - glLoadIdentity(); - glTranslated(-1, 1, 0); - glScaled(2.0/w, 2.0/h, 1.0); - glDisable(GL_DEPTH_TEST); - - double left = 80, top = -20, width = 240, height = 24; - glColor3d(0.9, 0.8, 0.8); - ssglAxisAlignedQuad(left, left+width, top, top-height); - ssglLineWidth(1); - glColor3d(0.0, 0.0, 0.0); - ssglAxisAlignedLineLoop(left, left+width, top, top-height); - - ssglInitializeBitmapFont(); - glColor3d(0, 0, 0); - glPushMatrix(); - glTranslated(left+8, top-20, 0); - glScaled(1, -1, 1); - ssglBitmapText(msg, Vector::From(0, 0, 0)); - glPopMatrix(); - glFlush(); - glDrawBuffer(GL_BACK); - } + hGroup hg = SK.groupOrder[i]; // The group may depend on entities or other groups, to define its // workplane geometry or for its operands. Those must already exist // in a previous group, so check them before generating. - if(PruneGroups(g->h)) + if(PruneGroups(hg)) goto pruned; - for(j = 0; j < SK.request.n; j++) { - Request *r = &(SK.request.elem[j]); - if(r->group.v != g->h.v) continue; + for(auto &req : SK.request) { + Request *r = &req; + if(r->group != hg) continue; r->Generate(&(SK.entity), &(SK.param)); } - g->Generate(&(SK.entity), &(SK.param)); + for(auto &con : SK.constraint) { + Constraint *c = &con; + if(c->group != hg) continue; + + c->Generate(&(SK.param)); + } + SK.GetGroup(hg)->Generate(&(SK.entity), &(SK.param)); // The requests and constraints depend on stuff in this or the // previous group, so check them after generating. - if(PruneRequests(g->h) || PruneConstraints(g->h)) + if(PruneRequests(hg) || PruneConstraints(hg)) goto pruned; // Use the previous values for params that we've seen before, as // initial guesses for the solver. - for(j = 0; j < SK.param.n; j++) { - Param *newp = &(SK.param.elem[j]); + for(auto &p : SK.param) { + Param *newp = &p; if(newp->known) continue; Param *prevp = prev.FindByIdNoOops(newp->h); @@ -299,18 +256,21 @@ void SolveSpaceUI::GenerateAll(GenerateType type, bool andFindFree, bool genForB } } - if(g->h.v == Group::HGROUP_REFERENCES.v) { + if(hg == Group::HGROUP_REFERENCES) { ForceReferences(); - g->solved.how = System::SOLVED_OKAY; + Group *g = SK.GetGroup(hg); + g->solved.how = SolveResult::OKAY; g->clean = true; } else { + // this i is an index in groupOrder if(i >= first && i <= last) { // The group falls inside the range, so really solve it, // and then regenerate the mesh based on the solved stuff. + Group *g = SK.GetGroup(hg); if(genForBBox) { - SolveGroupAndReport(g->h, andFindFree); - } else { + SolveGroupAndReport(hg, andFindFree); g->GenerateLoops(); + } else { g->GenerateShellAndMesh(); g->clean = true; } @@ -318,8 +278,8 @@ void SolveSpaceUI::GenerateAll(GenerateType type, bool andFindFree, bool genForB // The group falls outside the range, so just assume that // it's good wherever we left it. The mesh is unchanged, // and the parameters must be marked as known. - for(j = 0; j < SK.param.n; j++) { - Param *newp = &(SK.param.elem[j]); + for(auto &p : SK.param) { + Param *newp = &p; Param *prevp = prev.FindByIdNoOops(newp->h); if(prevp) newp->known = true; @@ -329,8 +289,8 @@ void SolveSpaceUI::GenerateAll(GenerateType type, bool andFindFree, bool genForB } // And update any reference dimensions with their new values - for(i = 0; i < SK.constraint.n; i++) { - Constraint *c = &(SK.constraint.elem[i]); + for(auto &con : SK.constraint) { + Constraint *c = &con; if(c->reference) { c->ModifyToSatisfy(); } @@ -347,7 +307,7 @@ void SolveSpaceUI::GenerateAll(GenerateType type, bool andFindFree, bool genForB } prev.Clear(); - InvalidateGraphics(); + GW.Invalidate(); // Remove nonexistent selection items, for same reason we waited till // the end to put up a dialog box. @@ -392,6 +352,26 @@ void SolveSpaceUI::GenerateAll(GenerateType type, bool andFindFree, bool genForB FreeAllTemporary(); allConsistent = true; + SS.GW.persistentDirty = true; + SS.centerOfMass.dirty = true; + + endMillis = GetMilliseconds(); + + if(endMillis - startMillis > 30) { + const char *typeStr = ""; + switch(type) { + case Generate::DIRTY: typeStr = "DIRTY"; break; + case Generate::ALL: typeStr = "ALL"; break; + case Generate::REGEN: typeStr = "REGEN"; break; + case Generate::UNTIL_ACTIVE: typeStr = "UNTIL_ACTIVE"; break; + } + if(endMillis) + dbp("Generate::%s%s took %lld ms", + typeStr, + (genForBBox ? " (for bounding box)" : ""), + GetMilliseconds() - startMillis); + } + return; pruned: @@ -402,7 +382,7 @@ pruned: GenerateAll(type, andFindFree, genForBBox); } -void SolveSpaceUI::ForceReferences(void) { +void SolveSpaceUI::ForceReferences() { // Force the values of the parameters that define the three reference // coordinate systems. static const struct { @@ -419,6 +399,7 @@ void SolveSpaceUI::ForceReferences(void) { // The origin for our coordinate system, always zero Entity *origin = SK.GetEntity(wrkpl->point[0]); origin->PointForceTo(Vector::From(0, 0, 0)); + origin->construction = true; SK.GetParam(origin->param[0])->known = true; SK.GetParam(origin->param[1])->known = true; SK.GetParam(origin->param[2])->known = true; @@ -432,7 +413,13 @@ void SolveSpaceUI::ForceReferences(void) { } } -void SolveSpaceUI::MarkDraggedParams(void) { +void SolveSpaceUI::UpdateCenterOfMass() { + SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); + SS.centerOfMass.position = m->GetCenterOfMass(); + SS.centerOfMass.dirty = false; +} + +void SolveSpaceUI::MarkDraggedParams() { sys.dragged.Clear(); for(int i = -1; i < SS.GW.pending.points.n; i++) { @@ -440,7 +427,7 @@ void SolveSpaceUI::MarkDraggedParams(void) { if(i == -1) { hp = SS.GW.pending.point; } else { - hp = SS.GW.pending.points.elem[i]; + hp = SS.GW.pending.points[i]; } if(!hp.v) continue; @@ -450,17 +437,21 @@ void SolveSpaceUI::MarkDraggedParams(void) { Entity *pt = SK.entity.FindByIdNoOops(hp); if(pt) { switch(pt->type) { - case Entity::POINT_N_TRANS: - case Entity::POINT_IN_3D: + case Entity::Type::POINT_N_TRANS: + case Entity::Type::POINT_IN_3D: + case Entity::Type::POINT_N_ROT_AXIS_TRANS: sys.dragged.Add(&(pt->param[0])); sys.dragged.Add(&(pt->param[1])); sys.dragged.Add(&(pt->param[2])); break; - case Entity::POINT_IN_2D: + case Entity::Type::POINT_IN_2D: sys.dragged.Add(&(pt->param[0])); sys.dragged.Add(&(pt->param[1])); break; + + default: // Only the entities above can be dragged. + break; } } } @@ -469,9 +460,12 @@ void SolveSpaceUI::MarkDraggedParams(void) { if(circ) { Entity *dist = SK.GetEntity(circ->distance); switch(dist->type) { - case Entity::DISTANCE: + case Entity::Type::DISTANCE: sys.dragged.Add(&(dist->param[0])); break; + + default: // Only the entities above can be dragged. + break; } } } @@ -479,13 +473,15 @@ void SolveSpaceUI::MarkDraggedParams(void) { Entity *norm = SK.entity.FindByIdNoOops(SS.GW.pending.normal); if(norm) { switch(norm->type) { - case Entity::NORMAL_IN_3D: + case Entity::Type::NORMAL_IN_3D: sys.dragged.Add(&(norm->param[0])); sys.dragged.Add(&(norm->param[1])); sys.dragged.Add(&(norm->param[2])); sys.dragged.Add(&(norm->param[3])); break; - // other types are locked, so not draggable + + default: // Only the entities above can be dragged. + break; } } } @@ -495,56 +491,76 @@ void SolveSpaceUI::SolveGroupAndReport(hGroup hg, bool andFindFree) { SolveGroup(hg, andFindFree); Group *g = SK.GetGroup(hg); - if(g->solved.how == System::REDUNDANT_OKAY) { - // Solve again, in case we lost a degree of freedom because of a numeric error. - SolveGroup(hg, andFindFree); - } - - bool isOkay = g->solved.how == System::SOLVED_OKAY || - (g->allowRedundant && g->solved.how == System::REDUNDANT_OKAY); - + bool isOkay = g->solved.how == SolveResult::OKAY || + (g->allowRedundant && g->solved.how == SolveResult::REDUNDANT_OKAY); if(!isOkay || (isOkay && !g->IsSolvedOkay())) { TextWindow::ReportHowGroupSolved(g->h); } } -void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) { - int i; +void SolveSpaceUI::WriteEqSystemForGroup(hGroup hg) { // Clear out the system to be solved. sys.entity.Clear(); sys.param.Clear(); sys.eq.Clear(); // And generate all the params for requests in this group - for(i = 0; i < SK.request.n; i++) { - Request *r = &(SK.request.elem[i]); - if(r->group.v != hg.v) continue; + for(auto &req : SK.request) { + Request *r = &req; + if(r->group != hg) continue; r->Generate(&(sys.entity), &(sys.param)); } + for(auto &con : SK.constraint) { + Constraint *c = &con; + if(c->group != hg) continue; + + c->Generate(&(sys.param)); + } // And for the group itself Group *g = SK.GetGroup(hg); g->Generate(&(sys.entity), &(sys.param)); // Set the initial guesses for all the params - for(i = 0; i < sys.param.n; i++) { - Param *p = &(sys.param.elem[i]); + for(auto ¶m : sys.param) { + Param *p = ¶m; p->known = false; p->val = SK.GetParam(p->h)->val; } MarkDraggedParams(); +} + +void SolveSpaceUI::SolveGroup(hGroup hg, bool andFindFree) { + WriteEqSystemForGroup(hg); + Group *g = SK.GetGroup(hg); g->solved.remove.Clear(); - int how = sys.Solve(g, &(g->solved.dof), - &(g->solved.remove), true, andFindFree); + g->solved.findToFixTimeout = SS.timeoutRedundantConstr; + SolveResult how = sys.Solve(g, NULL, + &(g->solved.dof), + &(g->solved.remove), + /*andFindBad=*/!g->allowRedundant, + /*andFindFree=*/andFindFree, + /*forceDofCheck=*/!g->dofCheckOk); + if(how == SolveResult::OKAY) { + g->dofCheckOk = true; + } g->solved.how = how; FreeAllTemporary(); } +SolveResult SolveSpaceUI::TestRankForGroup(hGroup hg, int *rank) { + WriteEqSystemForGroup(hg); + Group *g = SK.GetGroup(hg); + SolveResult result = sys.SolveRank(g, rank); + FreeAllTemporary(); + return result; +} + bool SolveSpaceUI::ActiveGroupsOkay() { for(int i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); + Group *g = SK.GetGroup(SK.groupOrder[i]); if(!g->IsSolvedOkay()) return false; - if(g->h.v == SS.GW.activeGroup.v) + if(g->h == SS.GW.activeGroup) break; } return true; diff --git a/src/glhelper.cpp b/src/glhelper.cpp deleted file mode 100644 index f789121..0000000 --- a/src/glhelper.cpp +++ /dev/null @@ -1,921 +0,0 @@ -//----------------------------------------------------------------------------- -// Helper functions that ultimately draw stuff with gl. -// -// Copyright 2008-2013 Jonathan Westhues. -//----------------------------------------------------------------------------- -#include -#include "solvespace.h" - -namespace SolveSpace { - -// A vector font. -#include "generated/vectorfont.table.h" - -// A bitmap font. -#include "generated/bitmapfont.table.h" - -static bool ColorLocked; -static bool DepthOffsetLocked; - -static const VectorGlyph &GetVectorGlyph(char32_t chr) { - int first = 0; - int last = sizeof(VectorFont) / sizeof(VectorGlyph); - while(first <= last) { - int mid = (first + last) / 2; - char32_t midChr = VectorFont[mid].character; - if(midChr > chr) { - last = mid - 1; // and first stays the same - continue; - } - if(midChr < chr) { - first = mid + 1; // and last stays the same - continue; - } - return VectorFont[mid]; - } - return GetVectorGlyph(0xfffd); // replacement character -} - -// Internally and in the UI, the vector font is sized using cap height. -#define FONT_SCALE(h) ((h)/(double)FONT_CAP_HEIGHT) -double ssglStrCapHeight(double h) -{ - return FONT_CAP_HEIGHT * FONT_SCALE(h) / SS.GW.scale; -} -double ssglStrFontSize(double h) -{ - return FONT_SIZE * FONT_SCALE(h) / SS.GW.scale; -} -double ssglStrWidth(const std::string &str, double h) -{ - int width = 0; - for(char32_t chr : ReadUTF8(str)) { - const VectorGlyph &glyph = GetVectorGlyph(chr); - if(glyph.baseCharacter != 0) { - const VectorGlyph &baseGlyph = GetVectorGlyph(glyph.baseCharacter); - width += max(glyph.advanceWidth, baseGlyph.advanceWidth); - } else { - width += glyph.advanceWidth; - } - } - return width * FONT_SCALE(h) / SS.GW.scale; -} -void ssglWriteTextRefCenter(const std::string &str, double h, Vector t, Vector u, Vector v, - ssglLineFn *fn, void *fndata) -{ - u = u.WithMagnitude(1); - v = v.WithMagnitude(1); - - double scale = FONT_SCALE(h)/SS.GW.scale; - double fh = ssglStrCapHeight(h); - double fw = ssglStrWidth(str, h); - - t = t.Plus(u.ScaledBy(-fw/2)); - t = t.Plus(v.ScaledBy(-fh/2)); - - // Apply additional offset to get an exact center alignment. - t = t.Plus(v.ScaledBy(-4608*scale)); - - ssglWriteText(str, h, t, u, v, fn, fndata); -} - -void ssglLineWidth(GLfloat width) { - // Intel GPUs with Mesa on *nix render thin lines poorly. - static bool workaroundChecked, workaroundEnabled; - if(!workaroundChecked) { - // ssglLineWidth can be called before GL is initialized - if(glGetString(GL_VENDOR)) { - workaroundChecked = true; - if(!strcmp((char*)glGetString(GL_VENDOR), "Intel Open Source Technology Center")) - workaroundEnabled = true; - } - } - - if(workaroundEnabled && width < 1.6f) - width = 1.6f; - - glLineWidth(width); -} - -static void LineDrawCallback(void *fndata, Vector a, Vector b) -{ - ssglLineWidth(1); - glBegin(GL_LINES); - ssglVertex3v(a); - ssglVertex3v(b); - glEnd(); -} - -Vector pixelAlign(Vector v) { - v = SS.GW.ProjectPoint3(v); - v.x = floor(v.x) + 0.5; - v.y = floor(v.y) + 0.5; - v = SS.GW.UnProjectPoint3(v); - return v; -} - -int ssglDrawCharacter(const VectorGlyph &glyph, Vector t, Vector o, Vector u, Vector v, - double scale, ssglLineFn *fn, void *fndata, bool gridFit) { - int advanceWidth = glyph.advanceWidth; - - if(glyph.baseCharacter != 0) { - const VectorGlyph &baseGlyph = GetVectorGlyph(glyph.baseCharacter); - int baseWidth = ssglDrawCharacter(baseGlyph, t, o, u, v, scale, fn, fndata, gridFit); - advanceWidth = max(glyph.advanceWidth, baseWidth); - } - - int actualWidth, offsetX; - if(gridFit) { - o.x += glyph.leftSideBearing; - offsetX = glyph.leftSideBearing; - actualWidth = glyph.boundingWidth; - if(actualWidth == 0) { - // Dot, "i", etc. - actualWidth = 1; - } - } else { - offsetX = 0; - actualWidth = advanceWidth; - } - - Vector tt = t; - tt = tt.Plus(u.ScaledBy(o.x * scale)); - tt = tt.Plus(v.ScaledBy(o.y * scale)); - - Vector tu = tt; - tu = tu.Plus(u.ScaledBy(actualWidth * scale)); - - Vector tv = tt; - tv = tv.Plus(v.ScaledBy(FONT_CAP_HEIGHT * scale)); - - if(gridFit) { - tt = pixelAlign(tt); - tu = pixelAlign(tu); - tv = pixelAlign(tv); - } - - tu = tu.Minus(tt).ScaledBy(1.0 / actualWidth); - tv = tv.Minus(tt).ScaledBy(1.0 / FONT_CAP_HEIGHT); - - const int16_t *data = glyph.data; - bool pen_up = true; - Vector prevp; - while(true) { - int16_t x = *data++; - int16_t y = *data++; - - if(x == PEN_UP && y == PEN_UP) { - if(pen_up) break; - pen_up = true; - } else { - Vector p = tt; - p = p.Plus(tu.ScaledBy(x - offsetX)); - p = p.Plus(tv.ScaledBy(y)); - if(!pen_up) fn(fndata, prevp, p); - prevp = p; - pen_up = false; - } - } - - return advanceWidth; -} - -void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v, - ssglLineFn *fn, void *fndata) -{ - if(!fn) fn = LineDrawCallback; - u = u.WithMagnitude(1); - v = v.WithMagnitude(1); - - // Perform grid-fitting only when the text is parallel to the view plane. - bool gridFit = !SS.exportMode && u.Equals(SS.GW.projRight) && v.Equals(SS.GW.projUp); - - double scale = FONT_SCALE(h) / SS.GW.scale; - Vector o = { 3840.0, 3840.0, 0.0 }; - for(char32_t chr : ReadUTF8(str)) { - const VectorGlyph &glyph = GetVectorGlyph(chr); - o.x += ssglDrawCharacter(glyph, t, o, u, v, scale, fn, fndata, gridFit); - } -} - -void ssglVertex3v(Vector u) -{ - glVertex3f((GLfloat)u.x, (GLfloat)u.y, (GLfloat)u.z); -} - -void ssglAxisAlignedQuad(double l, double r, double t, double b, bool lone) -{ - if(lone) glBegin(GL_QUADS); - glVertex2d(l, t); - glVertex2d(l, b); - glVertex2d(r, b); - glVertex2d(r, t); - if(lone) glEnd(); -} - -void ssglAxisAlignedLineLoop(double l, double r, double t, double b) -{ - glBegin(GL_LINE_LOOP); - glVertex2d(l, t); - glVertex2d(l, b); - glVertex2d(r, b); - glVertex2d(r, t); - glEnd(); -} - -static void FatLineEndcap(Vector p, Vector u, Vector v) -{ - // A table of cos and sin of (pi*i/10 + pi/2), as i goes from 0 to 10 - static const double Circle[11][2] = { - { 0.0000, 1.0000 }, - { -0.3090, 0.9511 }, - { -0.5878, 0.8090 }, - { -0.8090, 0.5878 }, - { -0.9511, 0.3090 }, - { -1.0000, 0.0000 }, - { -0.9511, -0.3090 }, - { -0.8090, -0.5878 }, - { -0.5878, -0.8090 }, - { -0.3090, -0.9511 }, - { 0.0000, -1.0000 }, - }; - glBegin(GL_TRIANGLE_FAN); - for(int i = 0; i <= 10; i++) { - double c = Circle[i][0], s = Circle[i][1]; - ssglVertex3v(p.Plus(u.ScaledBy(c)).Plus(v.ScaledBy(s))); - } - glEnd(); -} - -void ssglLine(const Vector &a, const Vector &b, double pixelWidth, bool maybeFat) { - if(!maybeFat || pixelWidth <= 3.0) { - glBegin(GL_LINES); - ssglVertex3v(a); - ssglVertex3v(b); - glEnd(); - } else { - ssglFatLine(a, b, pixelWidth / SS.GW.scale); - } -} - -void ssglPoint(Vector p, double pixelSize) -{ - if(/*!maybeFat || */pixelSize <= 3.0) { - glBegin(GL_LINES); - Vector u = SS.GW.projRight.WithMagnitude(pixelSize / SS.GW.scale / 2.0); - ssglVertex3v(p.Minus(u)); - ssglVertex3v(p.Plus(u)); - glEnd(); - } else { - Vector u = SS.GW.projRight.WithMagnitude(pixelSize / SS.GW.scale / 2.0); - Vector v = SS.GW.projUp.WithMagnitude(pixelSize / SS.GW.scale / 2.0); - - FatLineEndcap(p, u, v); - FatLineEndcap(p, u.ScaledBy(-1.0), v); - } -} - -void ssglStippledLine(Vector a, Vector b, double width, - int stippleType, double stippleScale, bool maybeFat) -{ - const char *stipplePattern; - switch(stippleType) { - case Style::STIPPLE_CONTINUOUS: ssglLine(a, b, width, maybeFat); return; - case Style::STIPPLE_DASH: stipplePattern = "- "; break; - case Style::STIPPLE_LONG_DASH: stipplePattern = "_ "; break; - case Style::STIPPLE_DASH_DOT: stipplePattern = "-."; break; - case Style::STIPPLE_DASH_DOT_DOT: stipplePattern = "-.."; break; - case Style::STIPPLE_DOT: stipplePattern = "."; break; - case Style::STIPPLE_FREEHAND: stipplePattern = "~"; break; - case Style::STIPPLE_ZIGZAG: stipplePattern = "~__"; break; - default: oops(); - } - ssglStippledLine(a, b, width, stipplePattern, stippleScale, maybeFat); -} - -void ssglStippledLine(Vector a, Vector b, double width, - const char *stipplePattern, double stippleScale, bool maybeFat) -{ - if(stipplePattern == NULL || *stipplePattern == 0) oops(); - - Vector dir = b.Minus(a); - double len = dir.Magnitude(); - dir = dir.WithMagnitude(1.0); - - const char *si = stipplePattern; - double end = len; - double ss = stippleScale / 2.0; - do { - double start = end; - switch(*si) { - case ' ': - end -= 1.0 * ss; - break; - - case '-': - start = max(start - 0.5 * ss, 0.0); - end = max(start - 2.0 * ss, 0.0); - if(start == end) break; - ssglLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), width, maybeFat); - end = max(end - 0.5 * ss, 0.0); - break; - - case '_': - end = max(end - 4.0 * ss, 0.0); - ssglLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), width, maybeFat); - break; - - case '.': - end = max(end - 0.5 * ss, 0.0); - if(end == 0.0) break; - ssglPoint(a.Plus(dir.ScaledBy(end)), width); - end = max(end - 0.5 * ss, 0.0); - break; - - case '~': { - Vector ab = b.Minus(a); - Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); - Vector abn = (ab.Cross(gn)).WithMagnitude(1); - abn = abn.Minus(gn.ScaledBy(gn.Dot(abn))); - double pws = 2.0 * width / SS.GW.scale; - - end = max(end - 0.5 * ss, 0.0); - Vector aa = a.Plus(dir.ScaledBy(start)); - Vector bb = a.Plus(dir.ScaledBy(end)) - .Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss))); - ssglLine(aa, bb, width, maybeFat); - if(end == 0.0) break; - - start = end; - end = max(end - 1.0 * ss, 0.0); - aa = a.Plus(dir.ScaledBy(end)) - .Plus(abn.ScaledBy(pws)) - .Minus(abn.ScaledBy(2.0 * pws * (start - end) / ss)); - ssglLine(bb, aa, width, maybeFat); - if(end == 0.0) break; - - start = end; - end = max(end - 0.5 * ss, 0.0); - bb = a.Plus(dir.ScaledBy(end)) - .Minus(abn.ScaledBy(pws)) - .Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss))); - ssglLine(aa, bb, width, maybeFat); - break; - } - - default: oops(); - } - if(*(++si) == 0) si = stipplePattern; - } while(end > 0.0); -} - -void ssglFatLine(Vector a, Vector b, double width) -{ - if(a.EqualsExactly(b)) return; - // The half-width of the line we're drawing. - double hw = width / 2; - Vector ab = b.Minus(a); - Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); - Vector abn = (ab.Cross(gn)).WithMagnitude(1); - abn = abn.Minus(gn.ScaledBy(gn.Dot(abn))); - // So now abn is normal to the projection of ab into the screen, so the - // line will always have constant thickness as the view is rotated. - - abn = abn.WithMagnitude(hw); - ab = gn.Cross(abn); - ab = ab. WithMagnitude(hw); - - // The body of a line is a quad - glBegin(GL_QUADS); - ssglVertex3v(a.Minus(abn)); - ssglVertex3v(b.Minus(abn)); - ssglVertex3v(b.Plus (abn)); - ssglVertex3v(a.Plus (abn)); - glEnd(); - // And the line has two semi-circular end caps. - FatLineEndcap(a, ab, abn); - FatLineEndcap(b, ab.ScaledBy(-1), abn); -} - - -void ssglLockColorTo(RgbaColor rgb) -{ - ColorLocked = false; - glColor3d(rgb.redF(), rgb.greenF(), rgb.blueF()); - ColorLocked = true; -} - -void ssglUnlockColor(void) -{ - ColorLocked = false; -} - -void ssglColorRGB(RgbaColor rgb) -{ - // Is there a bug in some graphics drivers where this is not equivalent - // to glColor3d? There seems to be... - ssglColorRGBa(rgb, 1.0); -} - -void ssglColorRGBa(RgbaColor rgb, double a) -{ - if(!ColorLocked) glColor4d(rgb.redF(), rgb.greenF(), rgb.blueF(), a); -} - -static void Stipple(bool forSel) -{ - static bool Init; - const int BYTES = (32*32)/8; - static GLubyte HoverMask[BYTES]; - static GLubyte SelMask[BYTES]; - if(!Init) { - int x, y; - for(x = 0; x < 32; x++) { - for(y = 0; y < 32; y++) { - int i = y*4 + x/8, b = x % 8; - int ym = y % 4, xm = x % 4; - for(int k = 0; k < 2; k++) { - if(xm >= 1 && xm <= 2 && ym >= 1 && ym <= 2) { - (k == 0 ? SelMask : HoverMask)[i] |= (0x80 >> b); - } - ym = (ym + 2) % 4; xm = (xm + 2) % 4; - } - } - } - Init = true; - } - - glEnable(GL_POLYGON_STIPPLE); - if(forSel) { - glPolygonStipple(SelMask); - } else { - glPolygonStipple(HoverMask); - } -} - -static void StippleTriangle(STriangle *tr, bool s, RgbaColor rgb) -{ - glEnd(); - glDisable(GL_LIGHTING); - ssglColorRGB(rgb); - Stipple(s); - glBegin(GL_TRIANGLES); - ssglVertex3v(tr->a); - ssglVertex3v(tr->b); - ssglVertex3v(tr->c); - glEnd(); - glEnable(GL_LIGHTING); - glDisable(GL_POLYGON_STIPPLE); - glBegin(GL_TRIANGLES); -} - -void ssglFillMesh(bool useSpecColor, RgbaColor specColor, - SMesh *m, uint32_t h, uint32_t s1, uint32_t s2) -{ - RgbaColor rgbHovered = Style::Color(Style::HOVERED), - rgbSelected = Style::Color(Style::SELECTED); - - glEnable(GL_NORMALIZE); - bool hasMaterial = false; - RgbaColor prevColor; - glBegin(GL_TRIANGLES); - for(int i = 0; i < m->l.n; i++) { - STriangle *tr = &(m->l.elem[i]); - - RgbaColor color; - if(useSpecColor) { - color = specColor; - } else { - color = tr->meta.color; - } - if(!hasMaterial || !color.Equals(prevColor)) { - GLfloat mpf[] = { color.redF(), color.greenF(), color.blueF(), color.alphaF() }; - glEnd(); - glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mpf); - prevColor = color; - hasMaterial = true; - glBegin(GL_TRIANGLES); - } - - if(tr->an.EqualsExactly(Vector::From(0, 0, 0))) { - // Compute the normal from the vertices - Vector n = tr->Normal(); - glNormal3d(n.x, n.y, n.z); - ssglVertex3v(tr->a); - ssglVertex3v(tr->b); - ssglVertex3v(tr->c); - } else { - // Use the exact normals that are specified - glNormal3d((tr->an).x, (tr->an).y, (tr->an).z); - ssglVertex3v(tr->a); - - glNormal3d((tr->bn).x, (tr->bn).y, (tr->bn).z); - ssglVertex3v(tr->b); - - glNormal3d((tr->cn).x, (tr->cn).y, (tr->cn).z); - ssglVertex3v(tr->c); - } - - if((s1 != 0 && tr->meta.face == s1) || - (s2 != 0 && tr->meta.face == s2)) - { - StippleTriangle(tr, true, rgbSelected); - } - if(h != 0 && tr->meta.face == h) { - StippleTriangle(tr, false, rgbHovered); - } - } - glEnd(); -} - -static void SSGL_CALLBACK Vertex(Vector *p) -{ - ssglVertex3v(*p); -} -void ssglFillPolygon(SPolygon *p) -{ - GLUtesselator *gt = gluNewTess(); - gluTessCallback(gt, GLU_TESS_BEGIN, (ssglCallbackFptr *)glBegin); - gluTessCallback(gt, GLU_TESS_END, (ssglCallbackFptr *)glEnd); - gluTessCallback(gt, GLU_TESS_VERTEX, (ssglCallbackFptr *)Vertex); - - ssglTesselatePolygon(gt, p); - - gluDeleteTess(gt); -} - -static void SSGL_CALLBACK Combine(double coords[3], void *vertexData[4], - float weight[4], void **outData) -{ - Vector *n = (Vector *)AllocTemporary(sizeof(Vector)); - n->x = coords[0]; - n->y = coords[1]; - n->z = coords[2]; - - *outData = n; -} -void ssglTesselatePolygon(GLUtesselator *gt, SPolygon *p) -{ - int i, j; - - gluTessCallback(gt, GLU_TESS_COMBINE, (ssglCallbackFptr *)Combine); - gluTessProperty(gt, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); - - Vector normal = p->normal; - glNormal3d(normal.x, normal.y, normal.z); - gluTessNormal(gt, normal.x, normal.y, normal.z); - - gluTessBeginPolygon(gt, NULL); - for(i = 0; i < p->l.n; i++) { - SContour *sc = &(p->l.elem[i]); - gluTessBeginContour(gt); - for(j = 0; j < (sc->l.n-1); j++) { - SPoint *sp = &(sc->l.elem[j]); - double ap[3]; - ap[0] = sp->p.x; - ap[1] = sp->p.y; - ap[2] = sp->p.z; - gluTessVertex(gt, ap, &(sp->p)); - } - gluTessEndContour(gt); - } - gluTessEndPolygon(gt); -} - -void ssglDebugPolygon(SPolygon *p) -{ - int i, j; - ssglLineWidth(2); - glPointSize(7); - glDisable(GL_DEPTH_TEST); - for(i = 0; i < p->l.n; i++) { - SContour *sc = &(p->l.elem[i]); - for(j = 0; j < (sc->l.n-1); j++) { - Vector a = (sc->l.elem[j]).p; - Vector b = (sc->l.elem[j+1]).p; - - ssglLockColorTo(RGBi(0, 0, 255)); - Vector d = (a.Minus(b)).WithMagnitude(-0); - glBegin(GL_LINES); - ssglVertex3v(a.Plus(d)); - ssglVertex3v(b.Minus(d)); - glEnd(); - ssglLockColorTo(RGBi(255, 0, 0)); - glBegin(GL_POINTS); - ssglVertex3v(a.Plus(d)); - ssglVertex3v(b.Minus(d)); - glEnd(); - } - } -} - -void ssglDrawEdges(SEdgeList *el, bool endpointsToo, hStyle hs) -{ - double lineWidth = Style::Width(hs); - int stippleType = Style::PatternType(hs); - double stippleScale = Style::StippleScaleMm(hs); - ssglLineWidth(float(lineWidth)); - ssglColorRGB(Style::Color(hs)); - - SEdge *se; - for(se = el->l.First(); se; se = el->l.NextAfter(se)) { - ssglStippledLine(se->a, se->b, lineWidth, stippleType, stippleScale, - /*maybeFat=*/true); - } - - if(endpointsToo) { - glPointSize(12); - glBegin(GL_POINTS); - for(se = el->l.First(); se; se = el->l.NextAfter(se)) { - ssglVertex3v(se->a); - ssglVertex3v(se->b); - } - glEnd(); - } -} - -void ssglDrawOutlines(SOutlineList *sol, Vector projDir, hStyle hs) -{ - double lineWidth = Style::Width(hs); - int stippleType = Style::PatternType(hs); - double stippleScale = Style::StippleScaleMm(hs); - ssglLineWidth((float)lineWidth); - ssglColorRGB(Style::Color(hs)); - - sol->FillOutlineTags(projDir); - for(SOutline *so = sol->l.First(); so; so = sol->l.NextAfter(so)) { - if(!so->tag) continue; - ssglStippledLine(so->a, so->b, lineWidth, stippleType, stippleScale, - /*maybeFat=*/true); - } -} - -void ssglDebugMesh(SMesh *m) -{ - int i; - ssglLineWidth(1); - glPointSize(7); - ssglDepthRangeOffset(1); - ssglUnlockColor(); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - ssglColorRGBa(RGBi(0, 255, 0), 1.0); - glBegin(GL_TRIANGLES); - for(i = 0; i < m->l.n; i++) { - STriangle *t = &(m->l.elem[i]); - if(t->tag) continue; - - ssglVertex3v(t->a); - ssglVertex3v(t->b); - ssglVertex3v(t->c); - } - glEnd(); - ssglDepthRangeOffset(0); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); -} - -void ssglMarkPolygonNormal(SPolygon *p) -{ - Vector tail = Vector::From(0, 0, 0); - int i, j, cnt = 0; - // Choose some reasonable center point. - for(i = 0; i < p->l.n; i++) { - SContour *sc = &(p->l.elem[i]); - for(j = 0; j < (sc->l.n-1); j++) { - SPoint *sp = &(sc->l.elem[j]); - tail = tail.Plus(sp->p); - cnt++; - } - } - if(cnt == 0) return; - tail = tail.ScaledBy(1.0/cnt); - - Vector gn = SS.GW.projRight.Cross(SS.GW.projUp); - Vector tip = tail.Plus((p->normal).WithMagnitude(40/SS.GW.scale)); - Vector arrow = (p->normal).WithMagnitude(15/SS.GW.scale); - - glColor3d(1, 1, 0); - glBegin(GL_LINES); - ssglVertex3v(tail); - ssglVertex3v(tip); - ssglVertex3v(tip); - ssglVertex3v(tip.Minus(arrow.RotatedAbout(gn, 0.6))); - ssglVertex3v(tip); - ssglVertex3v(tip.Minus(arrow.RotatedAbout(gn, -0.6))); - glEnd(); - glEnable(GL_LIGHTING); -} - -void ssglDepthRangeOffset(int units) -{ - if(!DepthOffsetLocked) { - // The size of this step depends on the resolution of the Z buffer; for - // a 16-bit buffer, this should be fine. - double d = units/60000.0; - glDepthRange(0.1-d, 1-d); - } -} - -void ssglDepthRangeLockToFront(bool yes) -{ - if(yes) { - DepthOffsetLocked = true; - glDepthRange(0, 0); - } else { - DepthOffsetLocked = false; - ssglDepthRangeOffset(0); - } -} - -const int BitmapFontChunkSize = 64 * 64; -static bool BitmapFontChunkInitialized[0x10000 / BitmapFontChunkSize]; -static int BitmapFontCurrentChunk = -1; - -static void CreateBitmapFontChunk(const uint8_t *source, size_t sourceLength, - int textureIndex) -{ - // Place the font in our texture in a two-dimensional grid. - // The maximum texture size that is reasonably supported is 1024x1024. - const size_t fontTextureSize = BitmapFontChunkSize*16*16; - uint8_t *fontTexture = (uint8_t *)malloc(fontTextureSize), - *mappedTexture = (uint8_t *)malloc(fontTextureSize); - - z_stream stream; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - stream.opaque = Z_NULL; - if(inflateInit(&stream) != Z_OK) - oops(); - - stream.next_in = (Bytef *)source; - stream.avail_in = sourceLength; - stream.next_out = fontTexture; - stream.avail_out = fontTextureSize; - if(inflate(&stream, Z_NO_FLUSH) != Z_STREAM_END) - oops(); - if(stream.avail_out != 0) - oops(); - - inflateEnd(&stream); - - for(int a = 0; a < BitmapFontChunkSize; a++) { - int row = a / 64, col = a % 64; - - for(int i = 0; i < 16; i++) { - memcpy(mappedTexture + row*64*16*16 + col*16 + i*64*16, - fontTexture + a*16*16 + i*16, - 16); - } - } - - free(fontTexture); - - glBindTexture(GL_TEXTURE_2D, textureIndex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, - 16*64, 64*16, - 0, - GL_ALPHA, GL_UNSIGNED_BYTE, - mappedTexture); - - free(mappedTexture); -} - -static void SwitchToBitmapFontChunkFor(char32_t chr) -{ - int plane = chr / BitmapFontChunkSize, - textureIndex = TEXTURE_BITMAP_FONT + plane; - - if(BitmapFontCurrentChunk != textureIndex) { - glEnd(); - - if(!BitmapFontChunkInitialized[plane]) { - CreateBitmapFontChunk(CompressedFontTexture[plane].data, - CompressedFontTexture[plane].length, - textureIndex); - BitmapFontChunkInitialized[plane] = true; - } else { - glBindTexture(GL_TEXTURE_2D, textureIndex); - } - - BitmapFontCurrentChunk = textureIndex; - - glBegin(GL_QUADS); - } -} - -void ssglInitializeBitmapFont() -{ - memset(BitmapFontChunkInitialized, 0, sizeof(BitmapFontChunkInitialized)); - BitmapFontCurrentChunk = -1; -} - -int ssglBitmapCharWidth(char32_t chr) { - if(!CodepointProperties[chr].exists) - chr = 0xfffd; // replacement character - - return CodepointProperties[chr].isWide ? 2 : 1; -} - -void ssglBitmapCharQuad(char32_t chr, double x, double y) -{ - int w, h; - - if(!CodepointProperties[chr].exists) - chr = 0xfffd; // replacement character - - h = 16; - if(chr >= 0xe000 && chr <= 0xefff) { - // Special character, like a checkbox or a radio button - w = 16; - x -= 3; - } else if(CodepointProperties[chr].isWide) { - // Wide (usually CJK or reserved) character - w = 16; - } else { - // Normal character - w = 8; - } - - if(chr != ' ' && chr != 0) { - int n = chr % BitmapFontChunkSize; - int row = n / 64, col = n % 64; - double s0 = col/64.0, - s1 = (col+1)/64.0, - t0 = row/64.0, - t1 = t0 + (w/16.0)/64; - - SwitchToBitmapFontChunkFor(chr); - - glTexCoord2d(s1, t0); - glVertex2d(x, y); - - glTexCoord2d(s1, t1); - glVertex2d(x + w, y); - - glTexCoord2d(s0, t1); - glVertex2d(x + w, y - h); - - glTexCoord2d(s0, t0); - glVertex2d(x, y - h); - } -} - -void ssglBitmapText(const std::string &str, Vector p) -{ - glEnable(GL_TEXTURE_2D); - glBegin(GL_QUADS); - for(char32_t chr : ReadUTF8(str)) { - ssglBitmapCharQuad(chr, p.x, p.y); - p.x += 8 * ssglBitmapCharWidth(chr); - } - glEnd(); - glDisable(GL_TEXTURE_2D); -} - -void ssglDrawPixelsWithTexture(uint8_t *data, int w, int h) -{ -#define MAX_DIM 32 - static uint8_t Texture[MAX_DIM*MAX_DIM*3]; - int i, j; - if(w > MAX_DIM || h > MAX_DIM) oops(); - - for(i = 0; i < w; i++) { - for(j = 0; j < h; j++) { - Texture[(j*MAX_DIM + i)*3 + 0] = data[(j*w + i)*3 + 0]; - Texture[(j*MAX_DIM + i)*3 + 1] = data[(j*w + i)*3 + 1]; - Texture[(j*MAX_DIM + i)*3 + 2] = data[(j*w + i)*3 + 2]; - } - } - - glBindTexture(GL_TEXTURE_2D, TEXTURE_DRAW_PIXELS); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, MAX_DIM, MAX_DIM, 0, - GL_RGB, GL_UNSIGNED_BYTE, Texture); - - glEnable(GL_TEXTURE_2D); - glBegin(GL_QUADS); - glTexCoord2d(0, 0); - glVertex2d(0, h); - - glTexCoord2d(((double)w)/MAX_DIM, 0); - glVertex2d(w, h); - - glTexCoord2d(((double)w)/MAX_DIM, ((double)h)/MAX_DIM); - glVertex2d(w, 0); - - glTexCoord2d(0, ((double)h)/MAX_DIM); - glVertex2d(0, 0); - glEnd(); - glDisable(GL_TEXTURE_2D); -} - -}; diff --git a/src/graphicswin.cpp b/src/graphicswin.cpp index e1531e5..5aed966 100644 --- a/src/graphicswin.cpp +++ b/src/graphicswin.cpp @@ -5,7 +5,17 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" -#include "config.h" + +typedef void MenuHandler(Command id); +using MenuKind = Platform::MenuItem::Indicator; +struct MenuEntry { + int level; // 0 == on menu bar, 1 == one level down + const char *label; // or NULL for a separator + Command cmd; // command ID + int accel; // keyboard accelerator + MenuKind kind; + MenuHandler *fn; +}; #define mView (&GraphicsWindow::MenuView) #define mEdit (&GraphicsWindow::MenuEdit) @@ -16,196 +26,357 @@ #define mGrp (&Group::MenuGroup) #define mAna (&SolveSpaceUI::MenuAnalyze) #define mHelp (&SolveSpaceUI::MenuHelp) -#define DEL DELETE_KEY -#define ESC ESCAPE_KEY +#define SHIFT_MASK 0x100 +#define CTRL_MASK 0x200 +#define FN_MASK 0x400 + #define S SHIFT_MASK #define C CTRL_MASK -#define F(k) (FUNCTION_KEY_BASE+(k)) -#define TN MENU_ITEM_NORMAL -#define TC MENU_ITEM_CHECK -#define TR MENU_ITEM_RADIO -const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { -//level -// label id accel ty fn -{ 0, "&File", 0, 0, TN, NULL }, -{ 1, "&New", MNU_NEW, C|'N', TN, mFile }, -{ 1, "&Open...", MNU_OPEN, C|'O', TN, mFile }, -{ 1, "Open &Recent", MNU_OPEN_RECENT, 0, TN, mFile }, -{ 1, "&Save", MNU_SAVE, C|'S', TN, mFile }, -{ 1, "Save &As...", MNU_SAVE_AS, 0, TN, mFile }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Export &Image...", MNU_EXPORT_PNG, 0, TN, mFile }, -{ 1, "Export 2d &View...", MNU_EXPORT_VIEW, 0, TN, mFile }, -{ 1, "Export 2d &Section...", MNU_EXPORT_SECTION, 0, TN, mFile }, -{ 1, "Export 3d &Wireframe...", MNU_EXPORT_WIREFRAME, 0, TN, mFile }, -{ 1, "Export Triangle &Mesh...", MNU_EXPORT_MESH, 0, TN, mFile }, -{ 1, "Export &Surfaces...", MNU_EXPORT_SURFACES,0, TN, mFile }, -{ 1, "Im&port...", MNU_IMPORT ,0, TN, mFile }, +#define F FN_MASK +#define KN MenuKind::NONE +#define KC MenuKind::CHECK_MARK +#define KR MenuKind::RADIO_MARK +const MenuEntry Menu[] = { +//lv label cmd accel kind +{ 0, N_("&File"), Command::NONE, 0, KN, NULL }, +{ 1, N_("&New"), Command::NEW, C|'n', KN, mFile }, +{ 1, N_("&Open..."), Command::OPEN, C|'o', KN, mFile }, +{ 1, N_("Open &Recent"), Command::OPEN_RECENT, 0, KN, mFile }, +{ 1, N_("&Save"), Command::SAVE, C|'s', KN, mFile }, +{ 1, N_("Save &As..."), Command::SAVE_AS, 0, KN, mFile }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Export &Image..."), Command::EXPORT_IMAGE, 0, KN, mFile }, +{ 1, N_("Export 2d &View..."), Command::EXPORT_VIEW, 0, KN, mFile }, +{ 1, N_("Export 2d &Section..."), Command::EXPORT_SECTION, 0, KN, mFile }, +{ 1, N_("Export 3d &Wireframe..."), Command::EXPORT_WIREFRAME, 0, KN, mFile }, +{ 1, N_("Export Triangle &Mesh..."), Command::EXPORT_MESH, 0, KN, mFile }, +{ 1, N_("Export &Surfaces..."), Command::EXPORT_SURFACES, 0, KN, mFile }, +{ 1, N_("Im&port..."), Command::IMPORT, 0, KN, mFile }, #ifndef __APPLE__ -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "E&xit", MNU_EXIT, C|'Q', TN, mFile }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("E&xit"), Command::EXIT, C|'q', KN, mFile }, #endif -{ 0, "&Edit", 0, 0, TN, NULL }, -{ 1, "&Undo", MNU_UNDO, C|'Z', TN, mEdit }, -{ 1, "&Redo", MNU_REDO, C|'Y', TN, mEdit }, -{ 1, "Re&generate All", MNU_REGEN_ALL, ' ', TN, mEdit }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Snap Selection to &Grid", MNU_SNAP_TO_GRID, '.', TN, mEdit }, -{ 1, "Rotate Imported &90°", MNU_ROTATE_90, '9', TN, mEdit }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Cu&t", MNU_CUT, C|'X', TN, mClip }, -{ 1, "&Copy", MNU_COPY, C|'C', TN, mClip }, -{ 1, "&Paste", MNU_PASTE, C|'V', TN, mClip }, -{ 1, "Paste &Transformed...", MNU_PASTE_TRANSFORM,C|'T', TN, mClip }, -{ 1, "&Delete", MNU_DELETE, DEL, TN, mClip }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Select &Edge Chain", MNU_SELECT_CHAIN, C|'E', TN, mEdit }, -{ 1, "Select &All", MNU_SELECT_ALL, C|'A', TN, mEdit }, -{ 1, "&Unselect All", MNU_UNSELECT_ALL, ESC, TN, mEdit }, - -{ 0, "&View", 0, 0, TN, NULL }, -{ 1, "Zoom &In", MNU_ZOOM_IN, '+', TN, mView }, -{ 1, "Zoom &Out", MNU_ZOOM_OUT, '-', TN, mView }, -{ 1, "Zoom To &Fit", MNU_ZOOM_TO_FIT, 'F', TN, mView }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Align View to &Workplane", MNU_ONTO_WORKPLANE, 'W', TN, mView }, -{ 1, "Nearest &Ortho View", MNU_NEAREST_ORTHO, F(2), TN, mView }, -{ 1, "Nearest &Isometric View", MNU_NEAREST_ISO, F(3), TN, mView }, -{ 1, "&Center View At Point", MNU_CENTER_VIEW, F(4), TN, mView }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Show Snap &Grid", MNU_SHOW_GRID, '>', TC, mView }, -{ 1, "Use &Perspective Projection", MNU_PERSPECTIVE_PROJ,'`', TC, mView }, -{ 1, NULL, 0, 0, TN, NULL }, -#if defined(__APPLE__) -{ 1, "Show Menu &Bar", MNU_SHOW_MENU_BAR, C|F(12), TC, mView }, -#endif -{ 1, "Show &Toolbar", MNU_SHOW_TOOLBAR, 0, TC, mView }, -{ 1, "Show Property Bro&wser", MNU_SHOW_TEXT_WND, '\t', TC, mView }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Dimensions in &Inches", MNU_UNITS_INCHES, 0, TR, mView }, -{ 1, "Dimensions in &Millimeters", MNU_UNITS_MM, 0, TR, mView }, -#if defined(HAVE_GTK) || defined(__APPLE__) -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "&Full Screen", MNU_FULL_SCREEN, C|F(11), TC, mView }, +{ 0, N_("&Edit"), Command::NONE, 0, KN, NULL }, +{ 1, N_("&Undo"), Command::UNDO, C|'z', KN, mEdit }, +{ 1, N_("&Redo"), Command::REDO, C|'y', KN, mEdit }, +{ 1, N_("Re&generate All"), Command::REGEN_ALL, ' ', KN, mEdit }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Snap Selection to &Grid"), Command::SNAP_TO_GRID, '.', KN, mEdit }, +{ 1, N_("Rotate Imported &90°"), Command::ROTATE_90, '9', KN, mEdit }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Cu&t"), Command::CUT, C|'x', KN, mClip }, +{ 1, N_("&Copy"), Command::COPY, C|'c', KN, mClip }, +{ 1, N_("&Paste"), Command::PASTE, C|'v', KN, mClip }, +{ 1, N_("Paste &Transformed..."), Command::PASTE_TRANSFORM, C|'t', KN, mClip }, +{ 1, N_("&Delete"), Command::DELETE, '\x7f', KN, mClip }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Select &Edge Chain"), Command::SELECT_CHAIN, C|'e', KN, mEdit }, +{ 1, N_("Select &All"), Command::SELECT_ALL, C|'a', KN, mEdit }, +{ 1, N_("&Unselect All"), Command::UNSELECT_ALL, '\x1b', KN, mEdit }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("&Line Styles..."), Command::EDIT_LINE_STYLES, 0, KN, mEdit }, +{ 1, N_("&View Projection..."), Command::VIEW_PROJECTION, 0, KN, mEdit }, +#ifndef __APPLE__ +{ 1, N_("Con&figuration..."), Command::CONFIGURATION, 0, KN, mEdit }, #endif -{ 0, "&New Group", 0, 0, TN, NULL }, -{ 1, "Sketch In &3d", MNU_GROUP_3D, S|'3', TN, mGrp }, -{ 1, "Sketch In New &Workplane", MNU_GROUP_WRKPL, S|'W', TN, mGrp }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Step &Translating", MNU_GROUP_TRANS, S|'T', TN, mGrp }, -{ 1, "Step &Rotating", MNU_GROUP_ROT, S|'R', TN, mGrp }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "E&xtrude", MNU_GROUP_EXTRUDE, S|'X', TN, mGrp }, -{ 1, "&Lathe", MNU_GROUP_LATHE, S|'L', TN, mGrp }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Link / Assemble...", MNU_GROUP_LINK, S|'I', TN, mGrp }, -{ 1, "Link Recent", MNU_GROUP_RECENT, 0, TN, mGrp }, - -{ 0, "&Sketch", 0, 0, TN, NULL }, -{ 1, "In &Workplane", MNU_SEL_WORKPLANE, '2', TR, mReq }, -{ 1, "Anywhere In &3d", MNU_FREE_IN_3D, '3', TR, mReq }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Datum &Point", MNU_DATUM_POINT, 'P', TN, mReq }, -{ 1, "&Workplane", MNU_WORKPLANE, 0, TN, mReq }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Line &Segment", MNU_LINE_SEGMENT, 'S', TN, mReq }, -{ 1, "C&onstruction Line Segment", MNU_CONSTR_SEGMENT, S|'S', TN, mReq }, -{ 1, "&Rectangle", MNU_RECTANGLE, 'R', TN, mReq }, -{ 1, "&Circle", MNU_CIRCLE, 'C', TN, mReq }, -{ 1, "&Arc of a Circle", MNU_ARC, 'A', TN, mReq }, -{ 1, "&Bezier Cubic Spline", MNU_CUBIC, 'B', TN, mReq }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "&Text in TrueType Font", MNU_TTF_TEXT, 'T', TN, mReq }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "To&ggle Construction", MNU_CONSTRUCTION, 'G', TN, mReq }, -{ 1, "Tangent &Arc at Point", MNU_TANGENT_ARC, S|'A', TN, mReq }, -{ 1, "Split Curves at &Intersection", MNU_SPLIT_CURVES, 'I', TN, mReq }, - -{ 0, "&Constrain", 0, 0, TN, NULL }, -{ 1, "&Distance / Diameter", MNU_DISTANCE_DIA, 'D', TN, mCon }, -{ 1, "Re&ference Dimension", MNU_REF_DISTANCE, S|'D', TN, mCon }, -{ 1, "A&ngle", MNU_ANGLE, 'N', TN, mCon }, -{ 1, "Reference An&gle", MNU_REF_ANGLE, S|'N', TN, mCon }, -{ 1, "Other S&upplementary Angle", MNU_OTHER_ANGLE, 'U', TN, mCon }, -{ 1, "Toggle R&eference Dim", MNU_REFERENCE, 'E', TN, mCon }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "&Horizontal", MNU_HORIZONTAL, 'H', TN, mCon }, -{ 1, "&Vertical", MNU_VERTICAL, 'V', TN, mCon }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "&On Point / Curve / Plane", MNU_ON_ENTITY, 'O', TN, mCon }, -{ 1, "E&qual Length / Radius / Angle", MNU_EQUAL, 'Q', TN, mCon }, -{ 1, "Length Ra&tio", MNU_RATIO, 'Z', TN, mCon }, -{ 1, "Length Diff&erence", MNU_DIFFERENCE, 'J', TN, mCon }, -{ 1, "At &Midpoint", MNU_AT_MIDPOINT, 'M', TN, mCon }, -{ 1, "S&ymmetric", MNU_SYMMETRIC, 'Y', TN, mCon }, -{ 1, "Para&llel / Tangent", MNU_PARALLEL, 'L', TN, mCon }, -{ 1, "&Perpendicular", MNU_PERPENDICULAR, '[', TN, mCon }, -{ 1, "Same Orient&ation", MNU_ORIENTED_SAME, 'X', TN, mCon }, -{ 1, "Lock Point Where &Dragged", MNU_WHERE_DRAGGED, ']', TN, mCon }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Comment", MNU_COMMENT, ';', TN, mCon }, - -{ 0, "&Analyze", 0, 0, TN, NULL }, -{ 1, "Measure &Volume", MNU_VOLUME, C|S|'V', TN, mAna }, -{ 1, "Measure &Area", MNU_AREA, C|S|'A', TN, mAna }, -{ 1, "Show &Interfering Parts", MNU_INTERFERENCE, C|S|'I', TN, mAna }, -{ 1, "Show &Naked Edges", MNU_NAKED_EDGES, C|S|'N', TN, mAna }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "Show Degrees of &Freedom", MNU_SHOW_DOF, C|S|'F', TN, mAna }, -{ 1, NULL, 0, 0, TN, NULL }, -{ 1, "&Trace Point", MNU_TRACE_PT, C|S|'T', TN, mAna }, -{ 1, "&Stop Tracing...", MNU_STOP_TRACING, C|S|'S', TN, mAna }, -{ 1, "Step &Dimension...", MNU_STEP_DIM, C|S|'D', TN, mAna }, - -{ 0, "&Help", 0, 0, TN, NULL }, -{ 1, "&Website / Manual", MNU_WEBSITE, 0, TN, mHelp }, +{ 0, N_("&View"), Command::NONE, 0, KN, mView }, +{ 1, N_("Zoom &In"), Command::ZOOM_IN, '+', KN, mView }, +{ 1, N_("Zoom &Out"), Command::ZOOM_OUT, '-', KN, mView }, +{ 1, N_("Zoom To &Fit"), Command::ZOOM_TO_FIT, 'f', KN, mView }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Align View to &Workplane"), Command::ONTO_WORKPLANE, 'w', KN, mView }, +{ 1, N_("Nearest &Ortho View"), Command::NEAREST_ORTHO, F|2, KN, mView }, +{ 1, N_("Nearest &Isometric View"), Command::NEAREST_ISO, F|3, KN, mView }, +{ 1, N_("&Center View At Point"), Command::CENTER_VIEW, F|4, KN, mView }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Show Snap &Grid"), Command::SHOW_GRID, '>', KC, mView }, +{ 1, N_("Use &Perspective Projection"), Command::PERSPECTIVE_PROJ, '`', KC, mView }, +{ 1, N_("Dimension &Units"), Command::NONE, 0, KN, NULL }, +{ 2, N_("Dimensions in &Millimeters"), Command::UNITS_MM, 0, KR, mView }, +{ 2, N_("Dimensions in M&eters"), Command::UNITS_METERS, 0, KR, mView }, +{ 2, N_("Dimensions in &Inches"), Command::UNITS_INCHES, 0, KR, mView }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Show &Toolbar"), Command::SHOW_TOOLBAR, 0, KC, mView }, +{ 1, N_("Show Property Bro&wser"), Command::SHOW_TEXT_WND, '\t', KC, mView }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("&Full Screen"), Command::FULL_SCREEN, C|F|11, KC, mView }, + +{ 0, N_("&New Group"), Command::NONE, 0, KN, mGrp }, +{ 1, N_("Sketch In &3d"), Command::GROUP_3D, S|'3', KN, mGrp }, +{ 1, N_("Sketch In New &Workplane"), Command::GROUP_WRKPL, S|'w', KN, mGrp }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Step &Translating"), Command::GROUP_TRANS, S|'t', KN, mGrp }, +{ 1, N_("Step &Rotating"), Command::GROUP_ROT, S|'r', KN, mGrp }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("E&xtrude"), Command::GROUP_EXTRUDE, S|'x', KN, mGrp }, +{ 1, N_("&Helix"), Command::GROUP_HELIX, S|'h', KN, mGrp }, +{ 1, N_("&Lathe"), Command::GROUP_LATHE, S|'l', KN, mGrp }, +{ 1, N_("Re&volve"), Command::GROUP_REVOLVE, S|'v', KN, mGrp }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Link / Assemble..."), Command::GROUP_LINK, S|'i', KN, mGrp }, +{ 1, N_("Link Recent"), Command::GROUP_RECENT, 0, KN, mGrp }, + +{ 0, N_("&Sketch"), Command::NONE, 0, KN, mReq }, +{ 1, N_("In &Workplane"), Command::SEL_WORKPLANE, '2', KR, mReq }, +{ 1, N_("Anywhere In &3d"), Command::FREE_IN_3D, '3', KR, mReq }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Datum &Point"), Command::DATUM_POINT, 'p', KN, mReq }, +{ 1, N_("&Workplane"), Command::WORKPLANE, 0, KN, mReq }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Line &Segment"), Command::LINE_SEGMENT, 's', KN, mReq }, +{ 1, N_("C&onstruction Line Segment"), Command::CONSTR_SEGMENT, S|'s', KN, mReq }, +{ 1, N_("&Rectangle"), Command::RECTANGLE, 'r', KN, mReq }, +{ 1, N_("&Circle"), Command::CIRCLE, 'c', KN, mReq }, +{ 1, N_("&Arc of a Circle"), Command::ARC, 'a', KN, mReq }, +{ 1, N_("&Bezier Cubic Spline"), Command::CUBIC, 'b', KN, mReq }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("&Text in TrueType Font"), Command::TTF_TEXT, 't', KN, mReq }, +{ 1, N_("&Image"), Command::IMAGE, 0, KN, mReq }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("To&ggle Construction"), Command::CONSTRUCTION, 'g', KN, mReq }, +{ 1, N_("Tangent &Arc at Point"), Command::TANGENT_ARC, S|'a', KN, mReq }, +{ 1, N_("Split Curves at &Intersection"), Command::SPLIT_CURVES, 'i', KN, mReq }, + +{ 0, N_("&Constrain"), Command::NONE, 0, KN, mCon }, +{ 1, N_("&Distance / Diameter"), Command::DISTANCE_DIA, 'd', KN, mCon }, +{ 1, N_("Re&ference Dimension"), Command::REF_DISTANCE, S|'d', KN, mCon }, +{ 1, N_("A&ngle"), Command::ANGLE, 'n', KN, mCon }, +{ 1, N_("Reference An&gle"), Command::REF_ANGLE, S|'n', KN, mCon }, +{ 1, N_("Other S&upplementary Angle"), Command::OTHER_ANGLE, 'u', KN, mCon }, +{ 1, N_("Toggle R&eference Dim"), Command::REFERENCE, 'e', KN, mCon }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("&Horizontal"), Command::HORIZONTAL, 'h', KN, mCon }, +{ 1, N_("&Vertical"), Command::VERTICAL, 'v', KN, mCon }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("&On Point / Curve / Plane"), Command::ON_ENTITY, 'o', KN, mCon }, +{ 1, N_("E&qual Length / Radius / Angle"), Command::EQUAL, 'q', KN, mCon }, +{ 1, N_("Length Ra&tio"), Command::RATIO, 'z', KN, mCon }, +{ 1, N_("Length Diff&erence"), Command::DIFFERENCE, 'j', KN, mCon }, +{ 1, N_("At &Midpoint"), Command::AT_MIDPOINT, 'm', KN, mCon }, +{ 1, N_("S&ymmetric"), Command::SYMMETRIC, 'y', KN, mCon }, +{ 1, N_("Para&llel / Tangent"), Command::PARALLEL, 'l', KN, mCon }, +{ 1, N_("&Perpendicular"), Command::PERPENDICULAR, '[', KN, mCon }, +{ 1, N_("Same Orient&ation"), Command::ORIENTED_SAME, 'x', KN, mCon }, +{ 1, N_("Lock Point Where &Dragged"), Command::WHERE_DRAGGED, ']', KN, mCon }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Comment"), Command::COMMENT, ';', KN, mCon }, + +{ 0, N_("&Analyze"), Command::NONE, 0, KN, mAna }, +{ 1, N_("Measure &Volume"), Command::VOLUME, C|S|'v', KN, mAna }, +{ 1, N_("Measure A&rea"), Command::AREA, C|S|'a', KN, mAna }, +{ 1, N_("Measure &Perimeter"), Command::PERIMETER, C|S|'p', KN, mAna }, +{ 1, N_("Show &Interfering Parts"), Command::INTERFERENCE, C|S|'i', KN, mAna }, +{ 1, N_("Show &Naked Edges"), Command::NAKED_EDGES, C|S|'n', KN, mAna }, +{ 1, N_("Show &Center of Mass"), Command::CENTER_OF_MASS, C|S|'c', KN, mAna }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("Show &Underconstrained Points"), Command::SHOW_DOF, C|S|'f', KN, mAna }, +{ 1, NULL, Command::NONE, 0, KN, NULL }, +{ 1, N_("&Trace Point"), Command::TRACE_PT, C|S|'t', KN, mAna }, +{ 1, N_("&Stop Tracing..."), Command::STOP_TRACING, C|S|'s', KN, mAna }, +{ 1, N_("Step &Dimension..."), Command::STEP_DIM, C|S|'d', KN, mAna }, + +{ 0, N_("&Help"), Command::NONE, 0, KN, mHelp }, +{ 1, N_("&Language"), Command::LOCALE, 0, KN, mHelp }, +{ 1, N_("&Website / Manual"), Command::WEBSITE, 0, KN, mHelp }, #ifndef __APPLE__ -{ 1, "&About", MNU_ABOUT, 0, TN, mHelp }, +{ 1, N_("&About"), Command::ABOUT, 0, KN, mHelp }, #endif - -{ -1, 0, 0, 0, TN, 0 } +{ -1, 0, Command::NONE, 0, KN, NULL } }; - -#undef DEL -#undef ESC #undef S #undef C #undef F -#undef TN -#undef TC -#undef TR +#undef KN +#undef KC +#undef KR + +void GraphicsWindow::ActivateCommand(Command cmd) { + for(int i = 0; Menu[i].level >= 0; i++) { + if(cmd == Menu[i].cmd) { + (Menu[i].fn)((Command)Menu[i].cmd); + break; + } + } +} -std::string SolveSpace::MakeAcceleratorLabel(int accel) { - if(!accel) return ""; +Platform::KeyboardEvent GraphicsWindow::AcceleratorForCommand(Command cmd) { + int rawAccel = 0; + for(int i = 0; Menu[i].level >= 0; i++) { + if(cmd == Menu[i].cmd) { + rawAccel = Menu[i].accel; + break; + } + } - std::string label; - if(accel & GraphicsWindow::CTRL_MASK) { - label += "Ctrl+"; + Platform::KeyboardEvent accel = {}; + accel.type = Platform::KeyboardEvent::Type::PRESS; + if(rawAccel & SHIFT_MASK) { + accel.shiftDown = true; } - if(accel & GraphicsWindow::SHIFT_MASK) { - label += "Shift+"; + if(rawAccel & CTRL_MASK) { + accel.controlDown = true; } - if(accel >= GraphicsWindow::FUNCTION_KEY_BASE + 1 && - accel <= GraphicsWindow::FUNCTION_KEY_BASE + 12) { - label += ssprintf("F%d", accel - GraphicsWindow::FUNCTION_KEY_BASE); - } else if(accel == '\t') { - label += "Tab"; - } else if(accel == ' ') { - label += "Space"; - } else if(accel == GraphicsWindow::ESCAPE_KEY) { - label += "Esc"; - } else if(accel == GraphicsWindow::DELETE_KEY) { - label += "Del"; + if(rawAccel & FN_MASK) { + accel.key = Platform::KeyboardEvent::Key::FUNCTION; + accel.num = rawAccel & 0xff; } else { - label += (char)(accel & 0xff); + accel.key = Platform::KeyboardEvent::Key::CHARACTER; + accel.chr = (char)(rawAccel & 0xff); } - return label; + + return accel; } -void GraphicsWindow::Init(void) { - scale = 5; +bool GraphicsWindow::KeyboardEvent(Platform::KeyboardEvent event) { + using Platform::KeyboardEvent; + + if(event.type == KeyboardEvent::Type::RELEASE) + return true; + + if(event.key == KeyboardEvent::Key::CHARACTER) { + if(event.chr == '\b') { + // Treat backspace identically to escape. + MenuEdit(Command::UNSELECT_ALL); + return true; + } else if(event.chr == '=') { + // Treat = as +. This is specific to US (and US-compatible) keyboard layouts, + // but makes zooming from keyboard much more usable on these. + // Ideally we'd have a platform-independent way of binding to a particular + // physical key regardless of shift status... + MenuView(Command::ZOOM_IN); + return true; + } + } + + // On some platforms, the OS does not handle some or all keyboard accelerators, + // so handle them here. + for(int i = 0; Menu[i].level >= 0; i++) { + if(AcceleratorForCommand(Menu[i].cmd).Equals(event)) { + ActivateCommand(Menu[i].cmd); + return true; + } + } + + return false; +} + +void GraphicsWindow::PopulateMainMenu() { + bool unique = false; + Platform::MenuBarRef mainMenu = Platform::GetOrCreateMainMenu(&unique); + if(unique) mainMenu->Clear(); + + Platform::MenuRef currentSubMenu; + std::vector subMenuStack; + for(int i = 0; Menu[i].level >= 0; i++) { + while(Menu[i].level > 0 && Menu[i].level <= (int)subMenuStack.size()) { + currentSubMenu = subMenuStack.back(); + subMenuStack.pop_back(); + } + + if(Menu[i].label == NULL) { + currentSubMenu->AddSeparator(); + continue; + } + + std::string label = Translate(Menu[i].label); + if(Menu[i].level == 0) { + currentSubMenu = mainMenu->AddSubMenu(label); + } else if(Menu[i].cmd == Command::OPEN_RECENT) { + openRecentMenu = currentSubMenu->AddSubMenu(label); + } else if(Menu[i].cmd == Command::GROUP_RECENT) { + linkRecentMenu = currentSubMenu->AddSubMenu(label); + } else if(Menu[i].cmd == Command::LOCALE) { + Platform::MenuRef localeMenu = currentSubMenu->AddSubMenu(label); + for(const Locale &locale : Locales()) { + localeMenu->AddItem(locale.displayName, [&]() { + SetLocale(locale.Culture()); + Platform::GetSettings()->FreezeString("Locale", locale.Culture()); + + SS.UpdateWindowTitles(); + PopulateMainMenu(); + EnsureValidActives(); + }); + } + } else if(Menu[i].fn == NULL) { + subMenuStack.push_back(currentSubMenu); + currentSubMenu = currentSubMenu->AddSubMenu(label); + } else { + Platform::MenuItemRef menuItem = currentSubMenu->AddItem(label); + menuItem->SetIndicator(Menu[i].kind); + if(Menu[i].accel != 0) { + menuItem->SetAccelerator(AcceleratorForCommand(Menu[i].cmd)); + } + menuItem->onTrigger = std::bind(Menu[i].fn, Menu[i].cmd); + + if(Menu[i].cmd == Command::SHOW_GRID) { + showGridMenuItem = menuItem; + } else if(Menu[i].cmd == Command::PERSPECTIVE_PROJ) { + perspectiveProjMenuItem = menuItem; + } else if(Menu[i].cmd == Command::SHOW_TOOLBAR) { + showToolbarMenuItem = menuItem; + } else if(Menu[i].cmd == Command::SHOW_TEXT_WND) { + showTextWndMenuItem = menuItem; + } else if(Menu[i].cmd == Command::FULL_SCREEN) { + fullScreenMenuItem = menuItem; + } else if(Menu[i].cmd == Command::UNITS_MM) { + unitsMmMenuItem = menuItem; + } else if(Menu[i].cmd == Command::UNITS_METERS) { + unitsMetersMenuItem = menuItem; + } else if(Menu[i].cmd == Command::UNITS_INCHES) { + unitsInchesMenuItem = menuItem; + } else if(Menu[i].cmd == Command::SEL_WORKPLANE) { + inWorkplaneMenuItem = menuItem; + } else if(Menu[i].cmd == Command::FREE_IN_3D) { + in3dMenuItem = menuItem; + } else if(Menu[i].cmd == Command::UNDO) { + undoMenuItem = menuItem; + } else if(Menu[i].cmd == Command::REDO) { + redoMenuItem = menuItem; + } + } + } + + PopulateRecentFiles(); + SS.UndoEnableMenus(); + + window->SetMenuBar(mainMenu); +} + +static void PopulateMenuWithPathnames(Platform::MenuRef menu, + std::vector pathnames, + std::function onTrigger) { + menu->Clear(); + if(pathnames.empty()) { + Platform::MenuItemRef menuItem = menu->AddItem(_("(no recent files)")); + menuItem->SetEnabled(false); + } else { + for(Platform::Path pathname : pathnames) { + Platform::MenuItemRef menuItem = menu->AddItem(pathname.raw, [=]() { + if(FileExists(pathname)) { + onTrigger(pathname); + } else { + Error(_("File '%s' does not exist."), pathname.raw.c_str()); + } + }, /*mnemonics=*/false); + } + } +} + +void GraphicsWindow::PopulateRecentFiles() { + PopulateMenuWithPathnames(openRecentMenu, SS.recentFiles, [](const Platform::Path &path) { + if(!SS.OkayToStartNewFile()) return; + SS.Load(path); + }); + + PopulateMenuWithPathnames(linkRecentMenu, SS.recentFiles, [](const Platform::Path &path) { + Group::MenuGroup(Command::GROUP_LINK, path); + }); +} + +void GraphicsWindow::Init() { + scale = 5; offset = Vector::From(0, 0, 0); projRight = Vector::From(1, 0, 0); projUp = Vector::From(0, 1, 0); @@ -216,36 +387,82 @@ void GraphicsWindow::Init(void) { orig.projUp = projUp; // And with the last group active - activeGroup = SK.groupOrder.elem[SK.groupOrder.n - 1]; + ssassert(!SK.groupOrder.IsEmpty(), + "Group order can't be empty since we will activate the last group."); + activeGroup = *SK.groupOrder.Last(); SK.GetGroup(activeGroup)->Activate(); showWorkplanes = false; showNormals = true; showPoints = true; + showConstruction = true; showConstraints = true; - showHdnLines = false; showShaded = true; showEdges = true; showMesh = false; showOutlines = false; + drawOccludedAs = DrawOccludedAs::INVISIBLE; showTextWindow = true; - ShowTextWindow(showTextWindow); showSnapGrid = false; context.active = false; + toolbarHovered = Command::NONE; + + if(!window) { + window = Platform::CreateWindow(); + if(window) { + using namespace std::placeholders; + // Do this first, so that if it causes an onRender event we don't try to paint without + // a canvas. + window->SetMinContentSize(720, 670); + window->onClose = std::bind(&SolveSpaceUI::MenuFile, Command::EXIT); + window->onRender = std::bind(&GraphicsWindow::Paint, this); + window->onKeyboardEvent = std::bind(&GraphicsWindow::KeyboardEvent, this, _1); + window->onMouseEvent = std::bind(&GraphicsWindow::MouseEvent, this, _1); + window->onSixDofEvent = std::bind(&GraphicsWindow::SixDofEvent, this, _1); + window->onEditingDone = std::bind(&GraphicsWindow::EditControlDone, this, _1); + PopulateMainMenu(); + } + } + + if(window) { + canvas = CreateRenderer(); + if(canvas) { + persistentCanvas = canvas->CreateBatch(); + persistentDirty = true; + } + } // Do this last, so that all the menus get updated correctly. ClearSuper(); } -void GraphicsWindow::AnimateOntoWorkplane(void) { +void GraphicsWindow::AnimateOntoWorkplane() { if(!LockedInWorkplane()) return; Entity *w = SK.GetEntity(ActiveWorkplane()); Quaternion quatf = w->Normal()->NormalGetNum(); + + // Get Z pointing vertical, if we're on turntable nav mode: + if(SS.turntableNav) { + Vector normalRight = quatf.RotationU(); + Vector normalUp = quatf.RotationV(); + Vector normal = normalRight.Cross(normalUp); + if(normalRight.z != 0) { + double theta = atan2(normalUp.z, normalRight.z); + theta -= atan2(1, 0); + normalRight = normalRight.RotatedAbout(normal, theta); + normalUp = normalUp.RotatedAbout(normal, theta); + quatf = Quaternion::From(normalRight, normalUp); + } + } + Vector offsetf = (SK.GetEntity(w->point[0])->PointGetNum()).ScaledBy(-1); + // If the view screen is open, then we need to refresh it. + SS.ScheduleShowTW(); + AnimateOnto(quatf, offsetf); } @@ -264,41 +481,45 @@ void GraphicsWindow::AnimateOnto(Quaternion quatf, Vector offsetf) { double mo = (offset0.Minus(offsetf)).Magnitude()*scale; // Animate transition, unless it's a tiny move. + int64_t t0 = GetMilliseconds(); int32_t dt = (mp < 0.01 && mo < 10) ? (-20) : (int32_t)(100 + 1000*mp + 0.4*mo); // Don't ever animate for longer than 2000 ms; we can get absurdly // long translations (as measured in pixels) if the user zooms out, moves, // and then zooms in again. if(dt > 2000) dt = 2000; - int64_t tn, t0 = GetMilliseconds(); - double s = 0; Quaternion dq = quatf.Times(quat0.Inverse()); - do { - offset = (offset0.ScaledBy(1 - s)).Plus(offsetf.ScaledBy(s)); - Quaternion quat = (dq.ToThe(s)).Times(quat0); - quat = quat.WithMagnitude(1); - - projRight = quat.RotationU(); - projUp = quat.RotationV(); - PaintGraphics(); - - tn = GetMilliseconds(); - s = (tn - t0)/((double)dt); - } while((tn - t0) < dt); - - projRight = quatf.RotationU(); - projUp = quatf.RotationV(); - offset = offsetf; - InvalidateGraphics(); - // If the view screen is open, then we need to refresh it. - SS.ScheduleShowTW(); + + if(!animateTimer) { + animateTimer = Platform::CreateTimer(); + } + animateTimer->onTimeout = [=] { + int64_t tn = GetMilliseconds(); + if((tn - t0) < dt) { + animateTimer->RunAfterNextFrame(); + + double s = (tn - t0)/((double)dt); + offset = (offset0.ScaledBy(1 - s)).Plus(offsetf.ScaledBy(s)); + Quaternion quat = (dq.ToThe(s)).Times(quat0).WithMagnitude(1); + + projRight = quat.RotationU(); + projUp = quat.RotationV(); + } else { + projRight = quatf.RotationU(); + projUp = quatf.RotationV(); + offset = offsetf; + } + window->Invalidate(); + }; + animateTimer->RunAfterNextFrame(); } void GraphicsWindow::HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin, - double *wmin, bool usePerspective) + double *wmin, bool usePerspective, + const Camera &camera) { double w; - Vector pp = ProjectPoint4(p, &w); + Vector pp = camera.ProjectPoint4(p, &w); // If usePerspective is true, then we calculate a perspective projection of the point. // If not, then we do a parallel projection regardless of the current // scale factor. @@ -313,15 +534,16 @@ void GraphicsWindow::HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *p *wmin = min(*wmin, w); } void GraphicsWindow::LoopOverPoints(const std::vector &entities, + const std::vector &constraints, const std::vector &faces, Point2d *pmax, Point2d *pmin, double *wmin, - bool usePerspective, bool includeMesh) { + bool usePerspective, bool includeMesh, + const Camera &camera) { - int i, j; for(Entity *e : entities) { if(e->IsPoint()) { - HandlePointForZoomToFit(e->PointGetNum(), pmax, pmin, wmin, usePerspective); - } else if(e->type == Entity::CIRCLE) { + HandlePointForZoomToFit(e->PointGetNum(), pmax, pmin, wmin, usePerspective, camera); + } else if(e->type == Entity::Type::CIRCLE) { // Lots of entities can extend outside the bbox of their points, // but circles are particularly bad. We want to get things halfway // reasonable without the mesh, because a zoom to fit is used to @@ -329,29 +551,37 @@ void GraphicsWindow::LoopOverPoints(const std::vector &entities, double r = e->CircleGetRadiusNum(); Vector c = SK.GetEntity(e->point[0])->PointGetNum(); Quaternion q = SK.GetEntity(e->normal)->NormalGetNum(); - for(j = 0; j < 4; j++) { + for(int j = 0; j < 4; j++) { Vector p = (j == 0) ? (c.Plus(q.RotationU().ScaledBy( r))) : (j == 1) ? (c.Plus(q.RotationU().ScaledBy(-r))) : (j == 2) ? (c.Plus(q.RotationV().ScaledBy( r))) : (c.Plus(q.RotationV().ScaledBy(-r))); - HandlePointForZoomToFit(p, pmax, pmin, wmin, usePerspective); + HandlePointForZoomToFit(p, pmax, pmin, wmin, usePerspective, camera); } } else { - // We have to iterate children points, because we can select entites without points + // We have to iterate children points, because we can select entities without points for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) { if(e->point[i].v == 0) break; Vector p = SK.GetEntity(e->point[i])->PointGetNum(); - HandlePointForZoomToFit(p, pmax, pmin, wmin, usePerspective); + HandlePointForZoomToFit(p, pmax, pmin, wmin, usePerspective, camera); } } } + for(Constraint *c : constraints) { + std::vector refs; + c->GetReferencePoints(camera, &refs); + for(Vector p : refs) { + HandlePointForZoomToFit(p, pmax, pmin, wmin, usePerspective, camera); + } + } + if(!includeMesh && faces.empty()) return; Group *g = SK.GetGroup(activeGroup); g->GenerateDisplayItems(); - for(i = 0; i < g->displayMesh.l.n; i++) { - STriangle *tr = &(g->displayMesh.l.elem[i]); + for(int i = 0; i < g->displayMesh.l.n; i++) { + STriangle *tr = &(g->displayMesh.l[i]); if(!includeMesh) { bool found = false; for(const hEntity &face : faces) { @@ -361,52 +591,69 @@ void GraphicsWindow::LoopOverPoints(const std::vector &entities, } if(!found) continue; } - HandlePointForZoomToFit(tr->a, pmax, pmin, wmin, usePerspective); - HandlePointForZoomToFit(tr->b, pmax, pmin, wmin, usePerspective); - HandlePointForZoomToFit(tr->c, pmax, pmin, wmin, usePerspective); + HandlePointForZoomToFit(tr->a, pmax, pmin, wmin, usePerspective, camera); + HandlePointForZoomToFit(tr->b, pmax, pmin, wmin, usePerspective, camera); + HandlePointForZoomToFit(tr->c, pmax, pmin, wmin, usePerspective, camera); } if(!includeMesh) return; - for(i = 0; i < g->polyLoops.l.n; i++) { - SContour *sc = &(g->polyLoops.l.elem[i]); - for(j = 0; j < sc->l.n; j++) { - HandlePointForZoomToFit(sc->l.elem[j].p, pmax, pmin, wmin, usePerspective); + for(int i = 0; i < g->polyLoops.l.n; i++) { + SContour *sc = &(g->polyLoops.l[i]); + for(int j = 0; j < sc->l.n; j++) { + HandlePointForZoomToFit(sc->l[j].p, pmax, pmin, wmin, usePerspective, camera); } } } void GraphicsWindow::ZoomToFit(bool includingInvisibles, bool useSelection) { + if(!window) return; + + scale = ZoomToFit(GetCamera(), includingInvisibles, useSelection); +} +double GraphicsWindow::ZoomToFit(const Camera &camera, + bool includingInvisibles, bool useSelection) { std::vector entities; + std::vector constraints; std::vector faces; if(useSelection) { for(int i = 0; i < selection.n; i++) { - Selection *s = &selection.elem[i]; - if(s->entity.v == 0) continue; - Entity *e = SK.entity.FindById(s->entity); - if(e->IsFace()) { - faces.push_back(e->h); - continue; + Selection *s = &selection[i]; + if(s->entity.v != 0) { + Entity *e = SK.entity.FindById(s->entity); + if(e->IsFace()) { + faces.push_back(e->h); + continue; + } + entities.push_back(e); + } + if(s->constraint.v != 0) { + Constraint *c = SK.constraint.FindById(s->constraint); + constraints.push_back(c); } - entities.push_back(e); } } - bool selectionUsed = !entities.empty() || !faces.empty(); + bool selectionUsed = !entities.empty() || !constraints.empty() || !faces.empty(); if(!selectionUsed) { - for(int i = 0; iIsPoint()) continue; - if(!includingInvisibles && !e->IsVisible()) continue; - entities.push_back(e); + if(e.IsPoint()) continue; + if(!includingInvisibles && !e.IsVisible()) continue; + entities.push_back(&e); + } + + for(Constraint &c : SK.constraint) { + if(!c.IsVisible()) continue; + constraints.push_back(&c); } } // On the first run, ignore perspective. Point2d pmax = { -1e12, -1e12 }, pmin = { 1e12, 1e12 }; double wmin = 1; - LoopOverPoints(entities, faces, &pmax, &pmin, &wmin, - /*usePerspective=*/false, /*includeMesh=*/!selectionUsed); + LoopOverPoints(entities, constraints, faces, &pmax, &pmin, &wmin, + /*usePerspective=*/false, /*includeMesh=*/!selectionUsed, + camera); double xm = (pmax.x + pmin.x)/2, ym = (pmax.y + pmin.y)/2; double dx = pmax.x - pmin.x, dy = pmax.y - pmin.y; @@ -415,12 +662,13 @@ void GraphicsWindow::ZoomToFit(bool includingInvisibles, bool useSelection) { projUp. ScaledBy(-ym)); // And based on this, we calculate the scale and offset + double scale; if(EXACT(dx == 0 && dy == 0)) { scale = 5; } else { double scalex = 1e12, scaley = 1e12; - if(EXACT(dx != 0)) scalex = 0.9*width /dx; - if(EXACT(dy != 0)) scaley = 0.9*height/dy; + if(EXACT(dx != 0)) scalex = 0.9*camera.width /dx; + if(EXACT(dy != 0)) scaley = 0.9*camera.height/dy; scale = min(scalex, scaley); scale = min(300.0, scale); @@ -431,68 +679,70 @@ void GraphicsWindow::ZoomToFit(bool includingInvisibles, bool useSelection) { pmax.x = -1e12; pmax.y = -1e12; pmin.x = 1e12; pmin.y = 1e12; wmin = 1; - LoopOverPoints(entities, faces, &pmax, &pmin, &wmin, - /*usePerspective=*/true, /*includeMesh=*/!selectionUsed); + LoopOverPoints(entities, constraints, faces, &pmax, &pmin, &wmin, + /*usePerspective=*/true, /*includeMesh=*/!selectionUsed, + camera); // Adjust the scale so that no points are behind the camera if(wmin < 0.1) { - double k = SS.CameraTangent(); + double k = camera.tangent; // w = 1+k*scale*z double zmin = (wmin - 1)/(k*scale); // 0.1 = 1 + k*scale*zmin // (0.1 - 1)/(k*zmin) = scale scale = min(scale, (0.1 - 1)/(k*zmin)); } + + return scale; } -void GraphicsWindow::MenuView(int id) { +void GraphicsWindow::MenuView(Command id) { switch(id) { - case MNU_ZOOM_IN: + case Command::ZOOM_IN: SS.GW.scale *= 1.2; SS.ScheduleShowTW(); break; - case MNU_ZOOM_OUT: + case Command::ZOOM_OUT: SS.GW.scale /= 1.2; SS.ScheduleShowTW(); break; - case MNU_ZOOM_TO_FIT: + case Command::ZOOM_TO_FIT: SS.GW.ZoomToFit(/*includingInvisibles=*/false, /*useSelection=*/true); SS.ScheduleShowTW(); break; - case MNU_SHOW_GRID: + case Command::SHOW_GRID: SS.GW.showSnapGrid = !SS.GW.showSnapGrid; + SS.GW.EnsureValidActives(); + SS.GW.Invalidate(); if(SS.GW.showSnapGrid && !SS.GW.LockedInWorkplane()) { - Message("No workplane is active, so the grid will not " - "appear."); + Message(_("No workplane is active, so the grid will not appear.")); } - SS.GW.EnsureValidActives(); - InvalidateGraphics(); break; - case MNU_PERSPECTIVE_PROJ: + case Command::PERSPECTIVE_PROJ: SS.usePerspectiveProj = !SS.usePerspectiveProj; + SS.GW.EnsureValidActives(); + SS.GW.Invalidate(); if(SS.cameraTangent < 1e-6) { - Error("The perspective factor is set to zero, so the view will " - "always be a parallel projection.\n\n" - "For a perspective projection, modify the perspective " - "factor in the configuration screen. A value around 0.3 " - "is typical."); + Error(_("The perspective factor is set to zero, so the view will " + "always be a parallel projection.\n\n" + "For a perspective projection, modify the perspective " + "factor in the configuration screen. A value around 0.3 " + "is typical.")); } - SS.GW.EnsureValidActives(); - InvalidateGraphics(); break; - case MNU_ONTO_WORKPLANE: + case Command::ONTO_WORKPLANE: if(SS.GW.LockedInWorkplane()) { SS.GW.AnimateOntoWorkplane(); - SS.ScheduleShowTW(); break; - } // if not in 2d mode fall through and use ORTHO logic - case MNU_NEAREST_ORTHO: - case MNU_NEAREST_ISO: { + } // if not in 2d mode use ORTHO logic + // fallthrough + case Command::NEAREST_ORTHO: + case Command::NEAREST_ISO: { static const Vector ortho[3] = { Vector::From(1, 0, 0), Vector::From(0, 1, 0), @@ -516,7 +766,7 @@ void GraphicsWindow::MenuView(int id) { Vector on = ou.Cross(ov); Vector u, v; - if(id == MNU_NEAREST_ORTHO || id == MNU_ONTO_WORKPLANE) { + if(id == Command::NEAREST_ORTHO || id == Command::ONTO_WORKPLANE) { u = ou; v = ov; } else { @@ -546,68 +796,69 @@ void GraphicsWindow::MenuView(int id) { break; } - case MNU_CENTER_VIEW: + case Command::CENTER_VIEW: SS.GW.GroupSelection(); if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) { Quaternion quat0; // Offset is the selected point, quaternion is same as before Vector pt = SK.GetEntity(SS.GW.gs.point[0])->PointGetNum(); quat0 = Quaternion::From(SS.GW.projRight, SS.GW.projUp); - SS.GW.AnimateOnto(quat0, pt.ScaledBy(-1)); SS.GW.ClearSelection(); + SS.GW.AnimateOnto(quat0, pt.ScaledBy(-1)); } else { - Error("Select a point; this point will become the center " - "of the view on screen."); + Error(_("Select a point; this point will become the center " + "of the view on screen.")); } break; - case MNU_SHOW_MENU_BAR: - ToggleMenuBar(); + case Command::SHOW_TOOLBAR: + SS.showToolbar = !SS.showToolbar; SS.GW.EnsureValidActives(); - InvalidateGraphics(); + SS.GW.Invalidate(); break; - case MNU_SHOW_TOOLBAR: - SS.showToolbar = !SS.showToolbar; + case Command::SHOW_TEXT_WND: + SS.GW.showTextWindow = !SS.GW.showTextWindow; SS.GW.EnsureValidActives(); - InvalidateGraphics(); break; - case MNU_SHOW_TEXT_WND: - SS.GW.showTextWindow = !SS.GW.showTextWindow; + case Command::UNITS_INCHES: + SS.viewUnits = Unit::INCHES; + SS.ScheduleShowTW(); SS.GW.EnsureValidActives(); break; - case MNU_UNITS_INCHES: - SS.viewUnits = SolveSpaceUI::UNIT_INCHES; + case Command::UNITS_MM: + SS.viewUnits = Unit::MM; SS.ScheduleShowTW(); SS.GW.EnsureValidActives(); break; - case MNU_UNITS_MM: - SS.viewUnits = SolveSpaceUI::UNIT_MM; + case Command::UNITS_METERS: + SS.viewUnits = Unit::METERS; SS.ScheduleShowTW(); SS.GW.EnsureValidActives(); break; - case MNU_FULL_SCREEN: - ToggleFullScreen(); + case Command::FULL_SCREEN: + SS.GW.window->SetFullScreen(!SS.GW.window->IsFullScreen()); SS.GW.EnsureValidActives(); break; - default: oops(); + default: ssassert(false, "Unexpected menu ID"); } - InvalidateGraphics(); + SS.GW.Invalidate(); } -void GraphicsWindow::EnsureValidActives(void) { +void GraphicsWindow::EnsureValidActives() { bool change = false; // The active group must exist, and not be the references. Group *g = SK.group.FindByIdNoOops(activeGroup); - if((!g) || (g->h.v == Group::HGROUP_REFERENCES.v)) { + if((!g) || (g->h == Group::HGROUP_REFERENCES)) { + // Not using range-for because this is used to find an index. int i; for(i = 0; i < SK.groupOrder.n; i++) { - if(SK.groupOrder.elem[i].v != Group::HGROUP_REFERENCES.v) { + if(SK.groupOrder[i] != Group::HGROUP_REFERENCES) { break; } } @@ -621,9 +872,9 @@ void GraphicsWindow::EnsureValidActives(void) { activeGroup = SS.CreateDefaultDrawingGroup(); // We've created the default group, but not the workplane entity; // do it now so that drawing mode isn't switched to "Free in 3d". - SS.GenerateAll(SolveSpaceUI::GENERATE_ALL); + SS.GenerateAll(SolveSpaceUI::Generate::ALL); } else { - activeGroup = SK.groupOrder.elem[i]; + activeGroup = SK.groupOrder[i]; } SK.GetGroup(activeGroup)->Activate(); change = true; @@ -634,7 +885,7 @@ void GraphicsWindow::EnsureValidActives(void) { Entity *e = SK.entity.FindByIdNoOops(ActiveWorkplane()); if(e) { hGroup hgw = e->group; - if(hgw.v != activeGroup.v && SS.GroupsInOrder(activeGroup, hgw)) { + if(hgw != activeGroup && SS.GroupsInOrder(activeGroup, hgw)) { // The active workplane is in a group that comes after the // active group; so any request or constraint will fail. SetWorkplaneFreeIn3d(); @@ -646,44 +897,43 @@ void GraphicsWindow::EnsureValidActives(void) { } } + if(!window) return; + // And update the checked state for various menus bool locked = LockedInWorkplane(); - RadioMenuById(MNU_FREE_IN_3D, !locked); - RadioMenuById(MNU_SEL_WORKPLANE, locked); + in3dMenuItem->SetActive(!locked); + inWorkplaneMenuItem->SetActive(locked); SS.UndoEnableMenus(); switch(SS.viewUnits) { - case SolveSpaceUI::UNIT_MM: - case SolveSpaceUI::UNIT_INCHES: + case Unit::MM: + case Unit::METERS: + case Unit::INCHES: break; default: - SS.viewUnits = SolveSpaceUI::UNIT_MM; + SS.viewUnits = Unit::MM; break; } - RadioMenuById(MNU_UNITS_MM, SS.viewUnits == SolveSpaceUI::UNIT_MM); - RadioMenuById(MNU_UNITS_INCHES, SS.viewUnits == SolveSpaceUI::UNIT_INCHES); + unitsMmMenuItem->SetActive(SS.viewUnits == Unit::MM); + unitsMetersMenuItem->SetActive(SS.viewUnits == Unit::METERS); + unitsInchesMenuItem->SetActive(SS.viewUnits == Unit::INCHES); - ShowTextWindow(SS.GW.showTextWindow); - CheckMenuById(MNU_SHOW_TEXT_WND, SS.GW.showTextWindow); + if(SS.TW.window) SS.TW.window->SetVisible(SS.GW.showTextWindow); + showTextWndMenuItem->SetActive(SS.GW.showTextWindow); -#if defined(__APPLE__) - CheckMenuById(MNU_SHOW_MENU_BAR, MenuBarIsVisible()); -#endif - CheckMenuById(MNU_SHOW_TOOLBAR, SS.showToolbar); - CheckMenuById(MNU_PERSPECTIVE_PROJ, SS.usePerspectiveProj); - CheckMenuById(MNU_SHOW_GRID, SS.GW.showSnapGrid); -#if defined(HAVE_GTK) || defined(__APPLE__) - CheckMenuById(MNU_FULL_SCREEN, FullScreenIsActive()); -#endif + showGridMenuItem->SetActive(SS.GW.showSnapGrid); + perspectiveProjMenuItem->SetActive(SS.usePerspectiveProj); + showToolbarMenuItem->SetActive(SS.showToolbar); + fullScreenMenuItem->SetActive(SS.GW.window->IsFullScreen()); if(change) SS.ScheduleShowTW(); } -void GraphicsWindow::SetWorkplaneFreeIn3d(void) { +void GraphicsWindow::SetWorkplaneFreeIn3d() { SK.GetGroup(activeGroup)->activeWorkplane = Entity::FREE_IN_3D; } -hEntity GraphicsWindow::ActiveWorkplane(void) { +hEntity GraphicsWindow::ActiveWorkplane() { Group *g = SK.group.FindByIdNoOops(activeGroup); if(g) { return g->activeWorkplane; @@ -691,22 +941,30 @@ hEntity GraphicsWindow::ActiveWorkplane(void) { return Entity::FREE_IN_3D; } } -bool GraphicsWindow::LockedInWorkplane(void) { - return (SS.GW.ActiveWorkplane().v != Entity::FREE_IN_3D.v); +bool GraphicsWindow::LockedInWorkplane() { + return (SS.GW.ActiveWorkplane() != Entity::FREE_IN_3D); } -void GraphicsWindow::ForceTextWindowShown(void) { +void GraphicsWindow::ForceTextWindowShown() { if(!showTextWindow) { showTextWindow = true; - CheckMenuById(MNU_SHOW_TEXT_WND, true); - ShowTextWindow(true); + showTextWndMenuItem->SetActive(true); + SS.TW.window->SetVisible(true); } } -void GraphicsWindow::DeleteTaggedRequests(void) { +void GraphicsWindow::DeleteTaggedRequests() { + Request *r; + // Delete any requests that were affected by this deletion. + for(r = SK.request.First(); r; r = SK.request.NextAfter(r)) { + if(r->workplane == Entity::FREE_IN_3D) continue; + if(!r->workplane.isFromRequest()) continue; + Request *wrkpl = SK.GetRequest(r->workplane.request()); + if(wrkpl->tag) + r->tag = 1; + } // Rewrite any point-coincident constraints that were affected by this // deletion. - Request *r; for(r = SK.request.First(); r; r = SK.request.NextAfter(r)) { if(!r->tag) continue; FixConstraintsForRequestBeingDeleted(r->h); @@ -716,13 +974,13 @@ void GraphicsWindow::DeleteTaggedRequests(void) { // An edit might be in progress for the just-deleted item. So // now it's not. - HideGraphicsEditControl(); + window->HideEditor(); SS.TW.HideEditControl(); // And clear out the selection, which could contain that item. ClearSuper(); // And regenerate to get rid of what it generates, plus anything // that references it (since the regen code checks for that). - SS.GenerateAll(SolveSpaceUI::GENERATE_ALL); + SS.GenerateAll(SolveSpaceUI::Generate::ALL); EnsureValidActives(); SS.ScheduleShowTW(); } @@ -745,22 +1003,22 @@ Vector GraphicsWindow::SnapToGrid(Vector p) { return pp.ScaleOutOfCsys(wu, wv, wn).Plus(wo); } -void GraphicsWindow::MenuEdit(int id) { +void GraphicsWindow::MenuEdit(Command id) { switch(id) { - case MNU_UNSELECT_ALL: + case Command::UNSELECT_ALL: SS.GW.GroupSelection(); // If there's nothing selected to de-select, and no operation // to cancel, then perhaps they want to return to the home // screen in the text window. if(SS.GW.gs.n == 0 && SS.GW.gs.constraints == 0 && - SS.GW.pending.operation == 0) + SS.GW.pending.operation == Pending::NONE) { - if(!(TextEditControlIsVisible() || - GraphicsEditControlIsVisible())) + if(!(SS.TW.window->IsEditorVisible() || + SS.GW.window->IsEditorVisible())) { - if(SS.TW.shown.screen == TextWindow::SCREEN_STYLE_INFO) { - SS.TW.GoToScreen(TextWindow::SCREEN_LIST_OF_STYLES); + if(SS.TW.shown.screen == TextWindow::Screen::STYLE_INFO) { + SS.TW.GoToScreen(TextWindow::Screen::LIST_OF_STYLES); } else { SS.TW.ClearSuper(); } @@ -770,6 +1028,7 @@ void GraphicsWindow::MenuEdit(int id) { SS.TW.HideEditControl(); SS.nakedEdges.Clear(); SS.justExportedInfo.draw = false; + SS.centerOfMass.draw = false; // This clears the marks drawn to indicate which points are // still free to drag. Param *p; @@ -778,32 +1037,33 @@ void GraphicsWindow::MenuEdit(int id) { } if(SS.exportMode) { SS.exportMode = false; - SS.GenerateAll(SolveSpaceUI::GENERATE_ALL); + SS.GenerateAll(SolveSpaceUI::Generate::ALL); } + SS.GW.persistentDirty = true; break; - case MNU_SELECT_ALL: { + case Command::SELECT_ALL: { Entity *e; for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(e->group.v != SS.GW.activeGroup.v) continue; + if(e->group != SS.GW.activeGroup) continue; if(e->IsFace() || e->IsDistance()) continue; if(!e->IsVisible()) continue; SS.GW.MakeSelected(e->h); } - InvalidateGraphics(); + SS.GW.Invalidate(); SS.ScheduleShowTW(); break; } - case MNU_SELECT_CHAIN: { + case Command::SELECT_CHAIN: { Entity *e; int newlySelected = 0; bool didSomething; do { didSomething = false; for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(e->group.v != SS.GW.activeGroup.v) continue; + if(e->group != SS.GW.activeGroup) continue; if(!e->HasEndpoints()) continue; if(!e->IsVisible()) continue; @@ -814,7 +1074,7 @@ void GraphicsWindow::MenuEdit(int id) { List *ls = &(SS.GW.selection); for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { if(!s->entity.v) continue; - if(s->entity.v == e->h.v) { + if(s->entity == e->h) { alreadySelected = true; continue; } @@ -837,16 +1097,15 @@ void GraphicsWindow::MenuEdit(int id) { } } } while(didSomething); + SS.GW.Invalidate(); + SS.ScheduleShowTW(); if(newlySelected == 0) { - Error("No additional entities share endpoints with the " - "selected entities."); + Error(_("No additional entities share endpoints with the selected entities.")); } - InvalidateGraphics(); - SS.ScheduleShowTW(); break; } - case MNU_ROTATE_90: { + case Command::ROTATE_90: { SS.GW.GroupSelection(); Entity *e = NULL; if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) { @@ -858,14 +1117,13 @@ void GraphicsWindow::MenuEdit(int id) { hGroup hg = e ? e->group : SS.GW.activeGroup; Group *g = SK.GetGroup(hg); - if(g->type != Group::LINKED) { - Error("To use this command, select a point or other " - "entity from an linked part, or make a link " - "group the active group."); + if(g->type != Group::Type::LINKED) { + Error(_("To use this command, select a point or other " + "entity from an linked part, or make a link " + "group the active group.")); break; } - SS.UndoRemember(); // Rotate by ninety degrees about the coordinate axis closest // to the screen normal. @@ -878,21 +1136,21 @@ void GraphicsWindow::MenuEdit(int id) { // and regenerate as necessary. SS.MarkGroupDirty(hg); - SS.GenerateAll(); break; } - case MNU_SNAP_TO_GRID: { + case Command::SNAP_TO_GRID: { if(!SS.GW.LockedInWorkplane()) { - Error("No workplane is active. Select a workplane to define " - "the plane for the snap grid."); + Error(_("No workplane is active. Activate a workplane " + "(with Sketch -> In Workplane) to define the plane " + "for the snap grid.")); break; } SS.GW.GroupSelection(); if(SS.GW.gs.points == 0 && SS.GW.gs.constraintLabels == 0) { - Error("Can't snap these items to grid; select points, " - "text comments, or constraints with a label. " - "To snap a line, select its endpoints."); + Error(_("Can't snap these items to grid; select points, " + "text comments, or constraints with a label. " + "To snap a line, select its endpoints.")); break; } SS.UndoRemember(); @@ -910,114 +1168,141 @@ void GraphicsWindow::MenuEdit(int id) { SS.MarkGroupDirty(ep->group); } else if(s->constraint.v) { Constraint *c = SK.GetConstraint(s->constraint); - Vector refp = c->GetReferencePos(); - c->disp.offset = c->disp.offset.Plus(SS.GW.SnapToGrid(refp).Minus(refp)); + std::vector refs; + c->GetReferencePoints(SS.GW.GetCamera(), &refs); + c->disp.offset = c->disp.offset.Plus(SS.GW.SnapToGrid(refs[0]).Minus(refs[0])); } } // Regenerate, with these points marked as dragged so that they // get placed as close as possible to our snap grid. - SS.GenerateAll(); SS.GW.ClearPending(); SS.GW.ClearSelection(); - InvalidateGraphics(); + SS.GW.Invalidate(); break; } - case MNU_UNDO: + case Command::UNDO: SS.UndoUndo(); break; - case MNU_REDO: + case Command::REDO: SS.UndoRedo(); break; - case MNU_REGEN_ALL: - SS.ReloadAllImported(); - SS.GenerateAll(SolveSpaceUI::GENERATE_UNTIL_ACTIVE); + case Command::REGEN_ALL: + SS.images.clear(); + SS.ReloadAllLinked(SS.saveFile); + SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE); SS.ScheduleShowTW(); break; - default: oops(); + case Command::EDIT_LINE_STYLES: + SS.TW.GoToScreen(TextWindow::Screen::LIST_OF_STYLES); + SS.GW.ForceTextWindowShown(); + SS.ScheduleShowTW(); + break; + case Command::VIEW_PROJECTION: + SS.TW.GoToScreen(TextWindow::Screen::EDIT_VIEW); + SS.GW.ForceTextWindowShown(); + SS.ScheduleShowTW(); + break; + case Command::CONFIGURATION: + SS.TW.GoToScreen(TextWindow::Screen::CONFIGURATION); + SS.GW.ForceTextWindowShown(); + SS.ScheduleShowTW(); + break; + + default: ssassert(false, "Unexpected menu ID"); } } -void GraphicsWindow::MenuRequest(int id) { +void GraphicsWindow::MenuRequest(Command id) { const char *s; switch(id) { - case MNU_SEL_WORKPLANE: { + case Command::SEL_WORKPLANE: { SS.GW.GroupSelection(); Group *g = SK.GetGroup(SS.GW.activeGroup); if(SS.GW.gs.n == 1 && SS.GW.gs.workplanes == 1) { // A user-selected workplane g->activeWorkplane = SS.GW.gs.entity[0]; - } else if(g->type == Group::DRAWING_WORKPLANE) { + SS.GW.EnsureValidActives(); + SS.ScheduleShowTW(); + } else if(g->type == Group::Type::DRAWING_WORKPLANE) { // The group's default workplane g->activeWorkplane = g->h.entity(0); - Message("No workplane selected. Activating default workplane " - "for this group."); - } - - if(!SS.GW.LockedInWorkplane()) { - Error("No workplane is selected, and the active group does " - "not have a default workplane. Try selecting a " - "workplane, or activating a sketch-in-new-workplane " - "group."); - break; + MessageAndRun([] { + // Align the view with the selected workplane + SS.GW.ClearSuper(); + SS.GW.AnimateOntoWorkplane(); + }, _("No workplane selected. Activating default workplane " + "for this group.")); + } else { + Error(_("No workplane is selected, and the active group does " + "not have a default workplane. Try selecting a " + "workplane, or activating a sketch-in-new-workplane " + "group.")); + //update checkboxes in the menus + SS.GW.EnsureValidActives(); } - // Align the view with the selected workplane - SS.GW.AnimateOntoWorkplane(); - SS.GW.ClearSuper(); - SS.ScheduleShowTW(); break; } - case MNU_FREE_IN_3D: + case Command::FREE_IN_3D: SS.GW.SetWorkplaneFreeIn3d(); SS.GW.EnsureValidActives(); SS.ScheduleShowTW(); - InvalidateGraphics(); + SS.GW.Invalidate(); break; - case MNU_TANGENT_ARC: + case Command::TANGENT_ARC: SS.GW.GroupSelection(); if(SS.GW.gs.n == 1 && SS.GW.gs.points == 1) { SS.GW.MakeTangentArc(); } else if(SS.GW.gs.n != 0) { - Error("Bad selection for tangent arc at point. Select a " - "single point, or select nothing to set up arc " - "parameters."); + Error(_("Bad selection for tangent arc at point. Select a " + "single point, or select nothing to set up arc " + "parameters.")); } else { - SS.TW.GoToScreen(TextWindow::SCREEN_TANGENT_ARC); + SS.TW.GoToScreen(TextWindow::Screen::TANGENT_ARC); SS.GW.ForceTextWindowShown(); SS.ScheduleShowTW(); - InvalidateGraphics(); // repaint toolbar + SS.GW.Invalidate(); // repaint toolbar } break; - case MNU_ARC: s = "click point on arc (draws anti-clockwise)"; goto c; - case MNU_DATUM_POINT: s = "click to place datum point"; goto c; - case MNU_LINE_SEGMENT: s = "click first point of line segment"; goto c; - case MNU_CONSTR_SEGMENT: s = "click first point of construction line segment"; goto c; - case MNU_CUBIC: s = "click first point of cubic segment"; goto c; - case MNU_CIRCLE: s = "click center of circle"; goto c; - case MNU_WORKPLANE: s = "click origin of workplane"; goto c; - case MNU_RECTANGLE: s = "click one corner of rectangle"; goto c; - case MNU_TTF_TEXT: s = "click top left of text"; goto c; + case Command::ARC: s = _("click point on arc (draws anti-clockwise)"); goto c; + case Command::DATUM_POINT: s = _("click to place datum point"); goto c; + case Command::LINE_SEGMENT: s = _("click first point of line segment"); goto c; + case Command::CONSTR_SEGMENT: + s = _("click first point of construction line segment"); goto c; + case Command::CUBIC: s = _("click first point of cubic segment"); goto c; + case Command::CIRCLE: s = _("click center of circle"); goto c; + case Command::WORKPLANE: s = _("click origin of workplane"); goto c; + case Command::RECTANGLE: s = _("click one corner of rectangle"); goto c; + case Command::TTF_TEXT: s = _("click top left of text"); goto c; + case Command::IMAGE: + if(!SS.ReloadLinkedImage(SS.saveFile, &SS.GW.pending.filename, + /*canCancel=*/true)) { + return; + } + s = _("click top left of image"); goto c; c: - SS.GW.pending.operation = id; + SS.GW.pending.operation = GraphicsWindow::Pending::COMMAND; + SS.GW.pending.command = id; SS.GW.pending.description = s; SS.ScheduleShowTW(); - InvalidateGraphics(); // repaint toolbar + SS.GW.Invalidate(); // repaint toolbar break; - case MNU_CONSTRUCTION: { - SS.UndoRemember(); + case Command::CONSTRUCTION: { SS.GW.GroupSelection(); if(SS.GW.gs.entities == 0) { - Error("No entities are selected. Select entities before " - "trying to toggle their construction state."); + Error(_("No entities are selected. Select entities before " + "trying to toggle their construction state.")); + break; } + SS.UndoRemember(); int i; for(i = 0; i < SS.GW.gs.entities; i++) { hEntity he = SS.GW.gs.entity[i]; @@ -1027,20 +1312,19 @@ c: SS.MarkGroupDirty(r->group); } SS.GW.ClearSelection(); - SS.GenerateAll(); break; } - case MNU_SPLIT_CURVES: + case Command::SPLIT_CURVES: SS.GW.SplitLinesOrCurves(); break; - default: oops(); + default: ssassert(false, "Unexpected menu ID"); } } -void GraphicsWindow::ClearSuper(void) { - HideGraphicsEditControl(); +void GraphicsWindow::ClearSuper() { + if(window) window->HideEditor(); ClearPending(); ClearSelection(); hover.Clear(); @@ -1054,36 +1338,39 @@ void GraphicsWindow::ToggleBool(bool *v) { // so not meaningful to show them and hide the shaded. if(!showShaded) showFaces = false; - // We might need to regenerate the mesh and edge list, since the edges - // wouldn't have been generated if they were previously hidden. - if(showEdges) (SK.GetGroup(activeGroup))->displayDirty = true; + // If the mesh or edges were previously hidden, they haven't been generated, + // and if we are going to show them, we need to generate them first. + Group *g = SK.GetGroup(SS.GW.activeGroup); + if(*v && (g->displayOutlines.l.IsEmpty() && (v == &showEdges || v == &showOutlines))) { + SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE); + } - SS.GenerateAll(); - InvalidateGraphics(); + Invalidate(/*clearPersistent=*/true); SS.ScheduleShowTW(); } -GraphicsWindow::SuggestedConstraint GraphicsWindow::SuggestLineConstraint(hRequest request) { - if(LockedInWorkplane()) { - Entity *ptA = SK.GetEntity(request.entity(1)), - *ptB = SK.GetEntity(request.entity(2)); +bool GraphicsWindow::SuggestLineConstraint(hRequest request, Constraint::Type *type) { + if(!(LockedInWorkplane() && SS.automaticLineConstraints)) + return false; - Expr *au, *av, *bu, *bv; + Entity *ptA = SK.GetEntity(request.entity(1)), + *ptB = SK.GetEntity(request.entity(2)); - ptA->PointGetExprsInWorkplane(ActiveWorkplane(), &au, &av); - ptB->PointGetExprsInWorkplane(ActiveWorkplane(), &bu, &bv); + Expr *au, *av, *bu, *bv; - double du = au->Minus(bu)->Eval(); - double dv = av->Minus(bv)->Eval(); + ptA->PointGetExprsInWorkplane(ActiveWorkplane(), &au, &av); + ptB->PointGetExprsInWorkplane(ActiveWorkplane(), &bu, &bv); - const double TOLERANCE_RATIO = 0.02; - if(fabs(dv) > LENGTH_EPS && fabs(du / dv) < TOLERANCE_RATIO) - return SUGGESTED_VERTICAL; - else if(fabs(du) > LENGTH_EPS && fabs(dv / du) < TOLERANCE_RATIO) - return SUGGESTED_HORIZONTAL; - else - return SUGGESTED_NONE; - } else { - return SUGGESTED_NONE; + double du = au->Minus(bu)->Eval(); + double dv = av->Minus(bv)->Eval(); + + const double TOLERANCE_RATIO = 0.02; + if(fabs(dv) > LENGTH_EPS && fabs(du / dv) < TOLERANCE_RATIO) { + *type = Constraint::Type::VERTICAL; + return true; + } else if(fabs(du) > LENGTH_EPS && fabs(dv / du) < TOLERANCE_RATIO) { + *type = Constraint::Type::HORIZONTAL; + return true; } + return false; } diff --git a/src/group.cpp b/src/group.cpp index 845cfff..1539e68 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -13,13 +13,11 @@ const hParam Param::NO_PARAM = { 0 }; const hGroup Group::HGROUP_REFERENCES = { 1 }; -#define gs (SS.GW.gs) - //----------------------------------------------------------------------------- // The group structure includes pointers to other dynamically-allocated // memory. This clears and frees them all. //----------------------------------------------------------------------------- -void Group::Clear(void) { +void Group::Clear() { polyLoops.Clear(); bezierLoops.Clear(); bezierOpens.Clear(); @@ -28,13 +26,12 @@ void Group::Clear(void) { thisShell.Clear(); runningShell.Clear(); displayMesh.Clear(); - displayEdges.Clear(); displayOutlines.Clear(); impMesh.Clear(); impShell.Clear(); impEntity.Clear(); // remap is the only one that doesn't get recreated when we regen - remap.Clear(); + remap.clear(); } void Group::AddParam(IdList *param, hParam hp, double v) { @@ -45,20 +42,16 @@ void Group::AddParam(IdList *param, hParam hp, double v) { param->Add(&pa); } -bool Group::IsVisible(void) { +bool Group::IsVisible() { if(!visible) return false; - if(SS.GroupsInOrder(SS.GW.activeGroup, h)) return false; + Group *active = SK.GetGroup(SS.GW.activeGroup); + if(order > active->order) return false; return true; } -int Group::GetNumConstraints(void) { - int num = 0; - for(int i = 0; i < SK.constraint.n; i++) { - Constraint *c = &SK.constraint.elem[i]; - if(c->group.v != h.v) continue; - num++; - } - return num; +size_t Group::GetNumConstraints() { + return std::count_if(SK.constraint.begin(), SK.constraint.end(), + [&](Constraint const &c) { return c.group == h; }); } Vector Group::ExtrusionGetVector() { @@ -71,30 +64,33 @@ void Group::ExtrusionForceVectorTo(const Vector &v) { SK.GetParam(h.param(2))->val = v.z; } -void Group::MenuGroup(int id) { +void Group::MenuGroup(Command id) { + MenuGroup(id, Platform::Path()); +} + +void Group::MenuGroup(Command id, Platform::Path linkFile) { + Platform::SettingsRef settings = Platform::GetSettings(); + Group g = {}; g.visible = true; g.color = RGBi(100, 100, 100); g.scale = 1; - - if(id >= RECENT_LINK && id < (RECENT_LINK + MAX_RECENT)) { - g.linkFile = RecentFile[id-RECENT_LINK]; - id = GraphicsWindow::MNU_GROUP_LINK; - } + g.linkFile = linkFile; SS.GW.GroupSelection(); + auto const &gs = SS.GW.gs; switch(id) { - case GraphicsWindow::MNU_GROUP_3D: - g.type = DRAWING_3D; - g.name = "sketch-in-3d"; + case Command::GROUP_3D: + g.type = Type::DRAWING_3D; + g.name = C_("group-name", "sketch-in-3d"); break; - case GraphicsWindow::MNU_GROUP_WRKPL: - g.type = DRAWING_WORKPLANE; - g.name = "sketch-in-plane"; + case Command::GROUP_WRKPL: + g.type = Type::DRAWING_WORKPLANE; + g.name = C_("group-name", "sketch-in-plane"); if(gs.points == 1 && gs.n == 1) { - g.subtype = WORKPLANE_BY_POINT_ORTHO; + g.subtype = Subtype::WORKPLANE_BY_POINT_ORTHO; Vector u = SS.GW.projRight, v = SS.GW.projUp; u = u.ClosestOrtho(); @@ -104,7 +100,7 @@ void Group::MenuGroup(int id) { g.predef.q = Quaternion::From(u, v); g.predef.origin = gs.point[0]; } else if(gs.points == 1 && gs.lineSegments == 2 && gs.n == 3) { - g.subtype = WORKPLANE_BY_LINE_SEGMENTS; + g.subtype = Subtype::WORKPLANE_BY_LINE_SEGMENTS; g.predef.origin = gs.point[0]; g.predef.entityB = gs.entity[0]; @@ -121,32 +117,57 @@ void Group::MenuGroup(int id) { } if(SS.GW.projRight.Dot(ut) < 0) g.predef.negateU = true; if(SS.GW.projUp. Dot(vt) < 0) g.predef.negateV = true; + } else if(gs.workplanes == 1 && gs.n == 1) { + if(gs.entity[0].isFromRequest()) { + Entity *wrkpl = SK.GetEntity(gs.entity[0]); + Entity *normal = SK.GetEntity(wrkpl->normal); + g.subtype = Subtype::WORKPLANE_BY_POINT_ORTHO; + g.predef.origin = wrkpl->point[0]; + g.predef.q = normal->NormalGetNum(); + } else { + Group *wrkplg = SK.GetGroup(gs.entity[0].group()); + g.subtype = wrkplg->subtype; + g.predef.origin = wrkplg->predef.origin; + if(wrkplg->subtype == Subtype::WORKPLANE_BY_LINE_SEGMENTS) { + g.predef.entityB = wrkplg->predef.entityB; + g.predef.entityC = wrkplg->predef.entityC; + g.predef.swapUV = wrkplg->predef.swapUV; + g.predef.negateU = wrkplg->predef.negateU; + g.predef.negateV = wrkplg->predef.negateV; + } else if(wrkplg->subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) { + g.predef.q = wrkplg->predef.q; + } else ssassert(false, "Unexpected workplane subtype"); + } } else { - Error("Bad selection for new sketch in workplane. This " - "group can be created with:\n\n" - " * a point (orthogonal to coordinate axes, " - "through the point)\n" - " * a point and two line segments (parallel to the " - "lines, through the point)\n"); + Error(_("Bad selection for new sketch in workplane. This " + "group can be created with:\n\n" + " * a point (through the point, orthogonal to coordinate axes)\n" + " * a point and two line segments (through the point, " + "parallel to the lines)\n" + " * a workplane (copy of the workplane)\n")); return; } break; - case GraphicsWindow::MNU_GROUP_EXTRUDE: + case Command::GROUP_EXTRUDE: if(!SS.GW.LockedInWorkplane()) { - Error("Select a workplane (Sketch -> In Workplane) before " - "extruding. The sketch will be extruded normal to the " - "workplane."); + Error(_("Activate a workplane (Sketch -> In Workplane) before " + "extruding. The sketch will be extruded normal to the " + "workplane.")); return; } - g.type = EXTRUDE; + g.type = Type::EXTRUDE; g.opA = SS.GW.activeGroup; g.predef.entityB = SS.GW.ActiveWorkplane(); - g.subtype = ONE_SIDED; - g.name = "extrude"; + g.subtype = Subtype::ONE_SIDED; + g.name = C_("group-name", "extrude"); break; - case GraphicsWindow::MNU_GROUP_LATHE: + case Command::GROUP_LATHE: + if(!SS.GW.LockedInWorkplane()) { + Error(_("Lathe operation can only be applied to planar sketches.")); + return; + } if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) { g.predef.origin = gs.point[0]; g.predef.entityB = gs.vector[0]; @@ -155,20 +176,76 @@ void Group::MenuGroup(int id) { g.predef.entityB = gs.entity[0]; // since a line segment is a vector } else { - Error("Bad selection for new lathe group. This group can " - "be created with:\n\n" - " * a point and a line segment or normal " - "(revolved about an axis parallel to line / " - "normal, through point)\n" - " * a line segment (revolved about line segment)\n"); + Error(_("Bad selection for new lathe group. This group can " + "be created with:\n\n" + " * a point and a line segment or normal " + "(revolved about an axis parallel to line / " + "normal, through point)\n" + " * a line segment (revolved about line segment)\n")); return; } - g.type = LATHE; + g.type = Type::LATHE; g.opA = SS.GW.activeGroup; - g.name = "lathe"; + g.name = C_("group-name", "lathe"); break; - case GraphicsWindow::MNU_GROUP_ROT: { + case Command::GROUP_REVOLVE: + if(!SS.GW.LockedInWorkplane()) { + Error(_("Revolve operation can only be applied to planar sketches.")); + return; + } + if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) { + g.predef.origin = gs.point[0]; + g.predef.entityB = gs.vector[0]; + } else if(gs.lineSegments == 1 && gs.n == 1) { + g.predef.origin = SK.GetEntity(gs.entity[0])->point[0]; + g.predef.entityB = gs.entity[0]; + // since a line segment is a vector + } else { + Error(_("Bad selection for new revolve group. This group can " + "be created with:\n\n" + " * a point and a line segment or normal " + "(revolved about an axis parallel to line / " + "normal, through point)\n" + " * a line segment (revolved about line segment)\n")); + return; + } + g.type = Type::REVOLVE; + g.opA = SS.GW.activeGroup; + g.valA = 2; + g.subtype = Subtype::ONE_SIDED; + g.name = C_("group-name", "revolve"); + break; + + case Command::GROUP_HELIX: + if(!SS.GW.LockedInWorkplane()) { + Error(_("Helix operation can only be applied to planar sketches.")); + return; + } + if(gs.points == 1 && gs.vectors == 1 && gs.n == 2) { + g.predef.origin = gs.point[0]; + g.predef.entityB = gs.vector[0]; + } else if(gs.lineSegments == 1 && gs.n == 1) { + g.predef.origin = SK.GetEntity(gs.entity[0])->point[0]; + g.predef.entityB = gs.entity[0]; + // since a line segment is a vector + } else { + Error(_("Bad selection for new helix group. This group can " + "be created with:\n\n" + " * a point and a line segment or normal " + "(revolved about an axis parallel to line / " + "normal, through point)\n" + " * a line segment (revolved about line segment)\n")); + return; + } + g.type = Type::HELIX; + g.opA = SS.GW.activeGroup; + g.valA = 2; + g.subtype = Subtype::ONE_SIDED; + g.name = C_("group-name", "helix"); + break; + + case Command::GROUP_ROT: { if(gs.points == 1 && gs.n == 1 && SS.GW.LockedInWorkplane()) { g.predef.origin = gs.point[0]; Entity *w = SK.GetEntity(SS.GW.ActiveWorkplane()); @@ -178,74 +255,62 @@ void Group::MenuGroup(int id) { g.predef.origin = gs.point[0]; g.predef.entityB = gs.vector[0]; } else { - Error("Bad selection for new rotation. This group can " - "be created with:\n\n" - " * a point, while locked in workplane (rotate " - "in plane, about that point)\n" - " * a point and a line or a normal (rotate about " - "an axis through the point, and parallel to " - "line / normal)\n"); + Error(_("Bad selection for new rotation. This group can " + "be created with:\n\n" + " * a point, while locked in workplane (rotate " + "in plane, about that point)\n" + " * a point and a line or a normal (rotate about " + "an axis through the point, and parallel to " + "line / normal)\n")); return; } - g.type = ROTATE; + g.type = Type::ROTATE; g.opA = SS.GW.activeGroup; g.valA = 3; - g.subtype = ONE_SIDED; - g.name = "rotate"; + g.subtype = Subtype::ONE_SIDED; + g.name = C_("group-name", "rotate"); break; } - case GraphicsWindow::MNU_GROUP_TRANS: - g.type = TRANSLATE; + case Command::GROUP_TRANS: + g.type = Type::TRANSLATE; g.opA = SS.GW.activeGroup; g.valA = 3; - g.subtype = ONE_SIDED; + g.subtype = Subtype::ONE_SIDED; g.predef.entityB = SS.GW.ActiveWorkplane(); g.activeWorkplane = SS.GW.ActiveWorkplane(); - g.name = "translate"; + g.name = C_("group-name", "translate"); break; - case GraphicsWindow::MNU_GROUP_LINK: { - g.type = LINKED; - if(g.linkFile.empty()) { - if(!GetOpenFile(&g.linkFile, "", SlvsFileFilter)) return; + case Command::GROUP_LINK: { + g.type = Type::LINKED; + g.meshCombine = CombineAs::ASSEMBLE; + if(g.linkFile.IsEmpty()) { + Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window); + dialog->AddFilters(Platform::SolveSpaceLinkFileFilters); + dialog->ThawChoices(settings, "LinkSketch"); + if(!dialog->RunModal()) return; + dialog->FreezeChoices(settings, "LinkSketch"); + g.linkFile = dialog->GetFilename(); } // Assign the default name of the group based on the name of // the linked file. - std::string groupName = g.linkFile; - size_t pos; - - pos = groupName.rfind(PATH_SEP); - if(pos != std::string::npos) - groupName.erase(0, pos + 1); - - pos = groupName.rfind('.'); - if(pos != std::string::npos) - groupName.erase(pos); - - for(size_t i = 0; i < groupName.length(); i++) { - if(!(isalnum(groupName[i]) || (unsigned)groupName[i] >= 0x80)) { + g.name = g.linkFile.FileStem(); + for(size_t i = 0; i < g.name.length(); i++) { + if(!(isalnum(g.name[i]) || (unsigned)g.name[i] >= 0x80)) { // convert punctuation to dashes - groupName[i] = '-'; + g.name[i] = '-'; } } - - if(groupName.length() > 0) { - g.name = groupName; - } else { - g.name = "link"; - } - - g.meshCombine = COMBINE_AS_ASSEMBLE; break; } - default: oops(); + default: ssassert(false, "Unexpected menu ID"); } // Copy color from the previous mesh-contributing group. - if(g.IsMeshGroup() && SK.groupOrder.n > 0) { + if(g.IsMeshGroup() && !SK.groupOrder.IsEmpty()) { Group *running = SK.GetRunningMeshGroupFor(SS.GW.activeGroup); if(running != NULL) { g.color = running->color; @@ -256,11 +321,11 @@ void Group::MenuGroup(int id) { SS.UndoRemember(); bool afterActive = false; - for(int i = 0; i < SK.groupOrder.n; i++) { - Group *gi = SK.GetGroup(SK.groupOrder.elem[i]); + for(hGroup hg : SK.groupOrder) { + Group *gi = SK.GetGroup(hg); if(afterActive) gi->order += 1; - if(gi->h.v == SS.GW.activeGroup.v) { + if(gi->h == SS.GW.activeGroup) { g.order = gi->order + 1; afterActive = true; } @@ -269,25 +334,24 @@ void Group::MenuGroup(int id) { SK.group.AddAndAssignId(&g); Group *gg = SK.GetGroup(g.h); - if(gg->type == LINKED) { - SS.ReloadAllImported(); + if(gg->type == Type::LINKED) { + SS.ReloadAllLinked(SS.saveFile); } gg->clean = false; SS.GW.activeGroup = gg->h; SS.GenerateAll(); - if(gg->type == DRAWING_WORKPLANE) { + if(gg->type == Type::DRAWING_WORKPLANE) { // Can't set the active workplane for this one until after we've // regenerated, because the workplane doesn't exist until then. gg->activeWorkplane = gg->h.entity(0); } gg->Activate(); - SS.GW.AnimateOntoWorkplane(); TextWindow::ScreenSelectGroup(0, gg->h.v); - SS.ScheduleShowTW(); + SS.GW.AnimateOntoWorkplane(); } void Group::TransformImportedBy(Vector t, Quaternion q) { - if(type != LINKED) oops(); + ssassert(type == Type::LINKED, "Expected a linked group"); hParam tx, ty, tz, qw, qx, qy, qz; tx = h.param(0); @@ -314,22 +378,39 @@ void Group::TransformImportedBy(Vector t, Quaternion q) { SK.GetParam(qz)->val = qg.vz; } -std::string Group::DescriptionString(void) { +bool Group::IsForcedToMeshBySource() const { + const Group *srcg = this; + if(type == Type::TRANSLATE || type == Type::ROTATE) { + // A step and repeat gets merged against the group's previous group, + // not our own previous group. + srcg = SK.GetGroup(opA); + if(srcg->forceToMesh) return true; + } + Group *g = srcg->RunningMeshGroup(); + if(g == NULL) return false; + return g->forceToMesh || g->IsForcedToMeshBySource(); +} + +bool Group::IsForcedToMesh() const { + return forceToMesh || IsForcedToMeshBySource(); +} + +std::string Group::DescriptionString() { if(name.empty()) { - return ssprintf("g%03x-(unnamed)", h.v); + return ssprintf("g%03x-%s", h.v, _("(unnamed)")); } else { return ssprintf("g%03x-%s", h.v, name.c_str()); } } -void Group::Activate(void) { - if(type == EXTRUDE || type == LINKED || type == LATHE || type == TRANSLATE || type == ROTATE) { +void Group::Activate() { + if(type == Type::EXTRUDE || type == Type::LINKED || type == Type::LATHE || + type == Type::REVOLVE || type == Type::HELIX || type == Type::TRANSLATE || type == Type::ROTATE) { SS.GW.showFaces = true; } else { SS.GW.showFaces = false; } SS.MarkGroupDirty(h); // for good measure; shouldn't be needed - SS.ScheduleGenerateAll(); SS.ScheduleShowTW(); } @@ -343,12 +424,12 @@ void Group::Generate(IdList *entity, gp = gp.WithMagnitude(200/SS.GW.scale); int a, i; switch(type) { - case DRAWING_3D: - break; + case Type::DRAWING_3D: + return; - case DRAWING_WORKPLANE: { + case Type::DRAWING_WORKPLANE: { Quaternion q; - if(subtype == WORKPLANE_BY_LINE_SEGMENTS) { + if(subtype == Subtype::WORKPLANE_BY_LINE_SEGMENTS) { Vector u = SK.GetEntity(predef.entityB)->VectorGetNum(); Vector v = SK.GetEntity(predef.entityC)->VectorGetNum(); u = u.WithMagnitude(1); @@ -359,13 +440,13 @@ void Group::Generate(IdList *entity, if(predef.negateU) u = u.ScaledBy(-1); if(predef.negateV) v = v.ScaledBy(-1); q = Quaternion::From(u, v); - } else if(subtype == WORKPLANE_BY_POINT_ORTHO) { + } else if(subtype == Subtype::WORKPLANE_BY_POINT_ORTHO) { // Already given, numerically. q = predef.q; - } else oops(); + } else ssassert(false, "Unexpected workplane subtype"); Entity normal = {}; - normal.type = Entity::NORMAL_N_COPY; + normal.type = Entity::Type::NORMAL_N_COPY; normal.numNormal = q; normal.point[0] = h.entity(2); normal.group = h; @@ -373,131 +454,259 @@ void Group::Generate(IdList *entity, entity->Add(&normal); Entity point = {}; - point.type = Entity::POINT_N_COPY; + point.type = Entity::Type::POINT_N_COPY; point.numPoint = SK.GetEntity(predef.origin)->PointGetNum(); + point.construction = true; point.group = h; point.h = h.entity(2); entity->Add(&point); Entity wp = {}; - wp.type = Entity::WORKPLANE; + wp.type = Entity::Type::WORKPLANE; wp.normal = normal.h; wp.point[0] = point.h; wp.group = h; wp.h = h.entity(0); entity->Add(&wp); - break; + return; } - case EXTRUDE: { + case Type::EXTRUDE: { AddParam(param, h.param(0), gn.x); AddParam(param, h.param(1), gn.y); AddParam(param, h.param(2), gn.z); int ai, af; - if(subtype == ONE_SIDED) { + if(subtype == Subtype::ONE_SIDED) { ai = 0; af = 2; - } else if(subtype == TWO_SIDED) { + } else if(subtype == Subtype::TWO_SIDED) { ai = -1; af = 1; - } else oops(); + } else ssassert(false, "Unexpected extrusion subtype"); // Get some arbitrary point in the sketch, that will be used // as a reference when defining top and bottom faces. hEntity pt = { 0 }; + // Not using range-for here because we're changing the size of entity in the loop. for(i = 0; i < entity->n; i++) { - Entity *e = &(entity->elem[i]); - if(e->group.v != opA.v) continue; + Entity *e = &(entity->Get(i)); + if(e->group != opA) continue; if(e->IsPoint()) pt = e->h; - e->CalculateNumerical(false); + e->CalculateNumerical(/*forExport=*/false); hEntity he = e->h; e = NULL; // As soon as I call CopyEntity, e may become invalid! That // adds entities, which may cause a realloc. CopyEntity(entity, SK.GetEntity(he), ai, REMAP_BOTTOM, h.param(0), h.param(1), h.param(2), - NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, - true, false); + NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, + CopyAs::N_TRANS); CopyEntity(entity, SK.GetEntity(he), af, REMAP_TOP, h.param(0), h.param(1), h.param(2), - NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, - true, false); + NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, + CopyAs::N_TRANS); MakeExtrusionLines(entity, he); } // Remapped versions of that arbitrary point will be used to // provide points on the plane faces. MakeExtrusionTopBottomFaces(entity, pt); - break; + return; } - case LATHE: { + case Type::LATHE: { Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum(); Vector axis_dir = SK.GetEntity(predef.entityB)->VectorGetNum(); - AddParam(param, h.param(0), axis_dir.x); - AddParam(param, h.param(1), axis_dir.y); - AddParam(param, h.param(2), axis_dir.z); - - // Remapped entity index. - int ai = 1; - + // Not using range-for here because we're changing the size of entity in the loop. for(i = 0; i < entity->n; i++) { - Entity *e = &(entity->elem[i]); - if(e->group.v != opA.v) continue; + Entity *e = &(entity->Get(i)); + if(e->group != opA) continue; - e->CalculateNumerical(false); + e->CalculateNumerical(/*forExport=*/false); hEntity he = e->h; // As soon as I call CopyEntity, e may become invalid! That // adds entities, which may cause a realloc. - CopyEntity(entity, SK.GetEntity(predef.origin), 0, ai, - h.param(0), h.param(1), h.param(2), - NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, - true, false); + // this is the regular copy of all entities CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_START, - h.param(0), h.param(1), h.param(2), - NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, - true, false); + NO_PARAM, NO_PARAM, NO_PARAM, + NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, + CopyAs::NUMERIC); + + e = &(entity->Get(i)); // because we copied. + if (e->IsPoint()) { + // for points this copy is used for the circle centers + CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_ARC_CENTER, + NO_PARAM, NO_PARAM, NO_PARAM, + NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, + CopyAs::NUMERIC); + MakeLatheCircles(entity, param, he, axis_pos, axis_dir); + }; + MakeLatheSurfacesSelectable(entity, he, axis_dir); + } + return; + } - CopyEntity(entity, SK.GetEntity(he), 0, REMAP_LATHE_END, - h.param(0), h.param(1), h.param(2), - NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, - true, false); + case Type::REVOLVE: { + // this was borrowed from LATHE and ROTATE + Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum(); + Vector axis_dir = SK.GetEntity(predef.entityB)->VectorGetNum(); + + // The center of rotation + AddParam(param, h.param(0), axis_pos.x); + AddParam(param, h.param(1), axis_pos.y); + AddParam(param, h.param(2), axis_pos.z); + // The rotation quaternion + AddParam(param, h.param(3), 30 * PI / 180); + AddParam(param, h.param(4), axis_dir.x); + AddParam(param, h.param(5), axis_dir.y); + AddParam(param, h.param(6), axis_dir.z); + + // Get some arbitrary point in the sketch, that will be used + // as a reference when defining end faces. + hEntity pt = { 0 }; - MakeLatheCircles(entity, param, he, axis_pos, axis_dir, ai); - ai++; + int ai = 0, af = 2; + if (subtype == Subtype::TWO_SIDED) + { + ai = -1; + af = 1; } - break; + // Not using range-for here because we're changing the size of entity in the loop. + for(i = 0; i < entity->n; i++) { + Entity *e = &(entity->Get(i)); + if(e->group != opA) + continue; + + if(e->IsPoint()) pt = e->h; + + e->CalculateNumerical(/*forExport=*/false); + hEntity he = e->h; + // one copy for each end of the revolved surface + CopyEntity(entity, e, ai, REMAP_LATHE_START, h.param(0), + h.param(1), h.param(2), h.param(3), h.param(4), h.param(5), + h.param(6), NO_PARAM, CopyAs::N_ROT_AA); + + e = &(entity->Get(i)); // because we copied. + CopyEntity(entity, e, af, REMAP_LATHE_END, h.param(0), + h.param(1), h.param(2), h.param(3), h.param(4), h.param(5), + h.param(6), NO_PARAM, CopyAs::N_ROT_AA); + + // Arcs are not generated for revolve groups, for now, because our current arc + // entity is not chiral, and dragging a revolve may break the arc by inverting it. + // MakeLatheCircles(entity, param, he, axis_pos, axis_dir); + MakeLatheSurfacesSelectable(entity, he, axis_dir); + } + MakeRevolveEndFaces(entity, pt, ai, af); + return; } - case TRANSLATE: { + case Type::HELIX: { + Vector axis_pos = SK.GetEntity(predef.origin)->PointGetNum(); + Vector axis_dir = SK.GetEntity(predef.entityB)->VectorGetNum(); + + // The center of rotation + AddParam(param, h.param(0), axis_pos.x); + AddParam(param, h.param(1), axis_pos.y); + AddParam(param, h.param(2), axis_pos.z); + // The rotation quaternion + AddParam(param, h.param(3), 30 * PI / 180); + AddParam(param, h.param(4), axis_dir.x); + AddParam(param, h.param(5), axis_dir.y); + AddParam(param, h.param(6), axis_dir.z); + // distance to translate along the rotation axis + AddParam(param, h.param(7), 20); + + // Get some arbitrary point in the sketch, that will be used + // as a reference when defining end faces. + hEntity pt = { 0 }; + + int ai = 0, af = 2; // initial and final number of transformations + if (subtype != Subtype::ONE_SIDED) + { + ai = -1; + af = 1; + } + + // Not using range-for here because we're changing the size of entity in the loop. + for(i = 0; i < entity->n; i++) { + Entity *e = &(entity->Get(i)); + if((e->group.v != opA.v) && !(e->h == predef.origin)) + continue; + + if(e->IsPoint()) pt = e->h; + + e->CalculateNumerical(/*forExport=*/false); + + // one copy for each end of the helix + CopyEntity(entity, e, ai, REMAP_LATHE_START, h.param(0), + h.param(1), h.param(2), h.param(3), h.param(4), h.param(5), + h.param(6), h.param(7), CopyAs::N_ROT_AXIS_TRANS); + + e = &(entity->Get(i)); // because we copied. + CopyEntity(entity, e, af, REMAP_LATHE_END, h.param(0), + h.param(1), h.param(2), h.param(3), h.param(4), h.param(5), + h.param(6), h.param(7), CopyAs::N_ROT_AXIS_TRANS); + + // For point entities on the axis, create a construction line + e = &(entity->Get(i)); + if(e->IsPoint()) { + Vector check = e->PointGetNum().Minus(axis_pos).Cross(axis_dir); + if (check.Dot(check) < LENGTH_EPS) { + //! @todo isn't this the same as &(ent[i])? + Entity *ep = SK.GetEntity(e->h); + Entity en = {}; + // A point gets extruded to form a line segment + en.point[0] = Remap(ep->h, REMAP_LATHE_START); + en.point[1] = Remap(ep->h, REMAP_LATHE_END); + en.group = h; + en.construction = ep->construction; + en.style = ep->style; + en.h = Remap(ep->h, REMAP_PT_TO_LINE); + en.type = Entity::Type::LINE_SEGMENT; + entity->Add(&en); + } + } + } + MakeRevolveEndFaces(entity, pt, ai, af); + return; + } + + case Type::TRANSLATE: { + // inherit meshCombine from source group + Group *srcg = SK.GetGroup(opA); + meshCombine = srcg->meshCombine; // The translation vector AddParam(param, h.param(0), gp.x); AddParam(param, h.param(1), gp.y); AddParam(param, h.param(2), gp.z); int n = (int)valA, a0 = 0; - if(subtype == ONE_SIDED && skipFirst) { + if(subtype == Subtype::ONE_SIDED && skipFirst) { a0++; n++; } for(a = a0; a < n; a++) { + // Not using range-for here because we're changing the size of entity in the loop. for(i = 0; i < entity->n; i++) { - Entity *e = &(entity->elem[i]); - if(e->group.v != opA.v) continue; + Entity *e = &(entity->Get(i)); + if(e->group != opA) continue; - e->CalculateNumerical(false); + e->CalculateNumerical(/*forExport=*/false); CopyEntity(entity, e, - a*2 - (subtype == ONE_SIDED ? 0 : (n-1)), + a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1)), (a == (n - 1)) ? REMAP_LAST : a, h.param(0), h.param(1), h.param(2), - NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, - true, false); + NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, NO_PARAM, + CopyAs::N_TRANS); } } - break; + return; } - case ROTATE: { + case Type::ROTATE: { + // inherit meshCombine from source group + Group *srcg = SK.GetGroup(opA); + meshCombine = srcg->meshCombine; // The center of rotation AddParam(param, h.param(0), gc.x); AddParam(param, h.param(1), gc.y); @@ -509,27 +718,28 @@ void Group::Generate(IdList *entity, AddParam(param, h.param(6), gn.z); int n = (int)valA, a0 = 0; - if(subtype == ONE_SIDED && skipFirst) { + if(subtype == Subtype::ONE_SIDED && skipFirst) { a0++; n++; } for(a = a0; a < n; a++) { + // Not using range-for here because we're changing the size of entity in the loop. for(i = 0; i < entity->n; i++) { - Entity *e = &(entity->elem[i]); - if(e->group.v != opA.v) continue; + Entity *e = &(entity->Get(i)); + if(e->group != opA) continue; - e->CalculateNumerical(false); + e->CalculateNumerical(/*forExport=*/false); CopyEntity(entity, e, - a*2 - (subtype == ONE_SIDED ? 0 : (n-1)), + a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1)), (a == (n - 1)) ? REMAP_LAST : a, h.param(0), h.param(1), h.param(2), - h.param(3), h.param(4), h.param(5), h.param(6), - false, true); + h.param(3), h.param(4), h.param(5), h.param(6), NO_PARAM, + CopyAs::N_ROT_AA); } } - break; + return; } - case LINKED: + case Type::LINKED: // The translation vector AddParam(param, h.param(0), gp.x); AddParam(param, h.param(1), gp.y); @@ -540,22 +750,22 @@ void Group::Generate(IdList *entity, AddParam(param, h.param(5), 0); AddParam(param, h.param(6), 0); + // Not using range-for here because we're changing the size of entity in the loop. for(i = 0; i < impEntity.n; i++) { - Entity *ie = &(impEntity.elem[i]); + Entity *ie = &(impEntity[i]); CopyEntity(entity, ie, 0, 0, h.param(0), h.param(1), h.param(2), - h.param(3), h.param(4), h.param(5), h.param(6), - false, false); + h.param(3), h.param(4), h.param(5), h.param(6), NO_PARAM, + CopyAs::N_ROT_TRANS); } - break; - - default: oops(); + return; } + ssassert(false, "Unexpected group type"); } bool Group::IsSolvedOkay() { - return this->solved.how == System::SOLVED_OKAY || - (this->allowRedundant && this->solved.how == System::REDUNDANT_OKAY); + return this->solved.how == SolveResult::OKAY || + (this->allowRedundant && this->solved.how == SolveResult::REDUNDANT_OKAY); } void Group::AddEq(IdList *l, Expr *expr, int index) { @@ -566,7 +776,7 @@ void Group::AddEq(IdList *l, Expr *expr, int index) { } void Group::GenerateEquations(IdList *l) { - if(type == LINKED) { + if(type == Type::LINKED) { // Normalize the quaternion ExprQuaternion q = { Expr::From(h.param(3)), @@ -574,7 +784,7 @@ void Group::GenerateEquations(IdList *l) { Expr::From(h.param(5)), Expr::From(h.param(6)) }; AddEq(l, (q.Magnitude())->Minus(Expr::From(1)), 0); - } else if(type == ROTATE) { + } else if(type == Type::ROTATE || type == Type::REVOLVE || type == Type::HELIX) { // The axis and center of rotation are specified numerically #define EC(x) (Expr::From(x)) #define EP(x) (Expr::From(h.param(x))) @@ -590,8 +800,8 @@ void Group::GenerateEquations(IdList *l) { AddEq(l, (EC(axis.z))->Minus(EP(6)), 5); #undef EC #undef EP - } else if(type == EXTRUDE) { - if(predef.entityB.v != Entity::FREE_IN_3D.v) { + } else if(type == Type::EXTRUDE) { + if(predef.entityB != Entity::FREE_IN_3D) { // The extrusion path is locked along a line, normal to the // specified workplane. Entity *w = SK.GetEntity(predef.entityB); @@ -605,8 +815,8 @@ void Group::GenerateEquations(IdList *l) { AddEq(l, u.Dot(extruden), 0); AddEq(l, v.Dot(extruden), 1); } - } else if(type == TRANSLATE) { - if(predef.entityB.v != Entity::FREE_IN_3D.v) { + } else if(type == Type::TRANSLATE) { + if(predef.entityB != Entity::FREE_IN_3D) { Entity *w = SK.GetEntity(predef.entityB); ExprVector n = w->Normal()->NormalExprsN(); ExprVector trans; @@ -619,30 +829,12 @@ void Group::GenerateEquations(IdList *l) { } hEntity Group::Remap(hEntity in, int copyNumber) { - // A hash table is used to accelerate the search - int hash = ((unsigned)(in.v*61 + copyNumber)) % REMAP_PRIME; - int i = remapCache[hash]; - if(i >= 0 && i < remap.n) { - EntityMap *em = &(remap.elem[i]); - if(em->input.v == in.v && em->copyNumber == copyNumber) { - return h.entity(em->h.v); - } - } - // but if we don't find it in the hash table, then linear search - for(i = 0; i < remap.n; i++) { - EntityMap *em = &(remap.elem[i]); - if(em->input.v == in.v && em->copyNumber == copyNumber) { - // We already have a mapping for this entity. - remapCache[hash] = i; - return h.entity(em->h.v); - } + auto it = remap.find({ in, copyNumber }); + if(it == remap.end()) { + std::tie(it, std::ignore) = + remap.insert({ { in, copyNumber }, { (uint32_t)remap.size() + 1 } }); } - // And if we still don't find it, then create a new entry. - EntityMap em; - em.input = in; - em.copyNumber = copyNumber; - remap.AddAndAssignId(&em); - return h.entity(em.h.v); + return h.entity(it->second.v); } void Group::MakeExtrusionLines(IdList *el, hEntity in) { @@ -657,9 +849,9 @@ void Group::MakeExtrusionLines(IdList *el, hEntity in) { en.construction = ep->construction; en.style = ep->style; en.h = Remap(ep->h, REMAP_PT_TO_LINE); - en.type = Entity::LINE_SEGMENT; + en.type = Entity::Type::LINE_SEGMENT; el->Add(&en); - } else if(ep->type == Entity::LINE_SEGMENT) { + } else if(ep->type == Entity::Type::LINE_SEGMENT) { // A line gets extruded to form a plane face; an endpoint of the // original line is a point in the plane, and the line is in the plane. Vector a = SK.GetEntity(ep->point[0])->PointGetNum(); @@ -676,20 +868,20 @@ void Group::MakeExtrusionLines(IdList *el, hEntity in) { en.construction = ep->construction; en.style = ep->style; en.h = Remap(ep->h, REMAP_LINE_TO_FACE); - en.type = Entity::FACE_XPROD; + en.type = Entity::Type::FACE_XPROD; el->Add(&en); } } -void Group::MakeLatheCircles(IdList *el, IdList *param, hEntity in, Vector pt, Vector axis, int ai) { +void Group::MakeLatheCircles(IdList *el, IdList *param, hEntity in, Vector pt, Vector axis) { Entity *ep = SK.GetEntity(in); Entity en = {}; if(ep->IsPoint()) { // A point gets revolved to form an arc. - en.point[0] = Remap(predef.origin, ai); + en.point[0] = Remap(ep->h, REMAP_LATHE_ARC_CENTER); en.point[1] = Remap(ep->h, REMAP_LATHE_START); - en.point[2] = Remap(ep->h, REMAP_LATHE_END); + en.point[2] = en.point[1]; //Remap(ep->h, REMAP_LATHE_END); // Get arc center and point on arc. Entity *pc = SK.GetEntity(en.point[0]); @@ -704,7 +896,7 @@ void Group::MakeLatheCircles(IdList *el, IdList *p en.construction = ep->construction; en.style = ep->style; en.h = Remap(ep->h, REMAP_PT_TO_ARC); - en.type = Entity::ARC_OF_CIRCLE; + en.type = Entity::Type::ARC_OF_CIRCLE; // Generate a normal. Entity n = {}; @@ -712,7 +904,7 @@ void Group::MakeLatheCircles(IdList *el, IdList *p n.h = Remap(ep->h, REMAP_PT_TO_NORMAL); n.group = en.group; n.style = en.style; - n.type = Entity::NORMAL_N_COPY; + n.type = Entity::Type::NORMAL_N_COPY; // Create basis for the normal. Vector nu = pp->numPoint.Minus(pc->numPoint).WithMagnitude(1.0); @@ -725,7 +917,14 @@ void Group::MakeLatheCircles(IdList *el, IdList *p el->Add(&n); en.normal = n.h; el->Add(&en); - } else if(ep->type == Entity::LINE_SEGMENT) { + } +} + +void Group::MakeLatheSurfacesSelectable(IdList *el, hEntity in, Vector axis) { + Entity *ep = SK.GetEntity(in); + + Entity en = {}; + if(ep->type == Entity::Type::LINE_SEGMENT) { // An axis-perpendicular line gets revolved to form a face. Vector a = SK.GetEntity(ep->point[0])->PointGetNum(); Vector b = SK.GetEntity(ep->point[1])->PointGetNum(); @@ -746,21 +945,67 @@ void Group::MakeLatheCircles(IdList *el, IdList *p en.construction = ep->construction; en.style = ep->style; en.h = Remap(ep->h, REMAP_LINE_TO_FACE); - en.type = Entity::FACE_NORMAL_PT; + en.type = Entity::Type::FACE_NORMAL_PT; en.point[0] = ep->point[0]; el->Add(&en); } } } +// For Revolve and Helix groups the end faces are remapped from an arbitrary +// point on the sketch. We reference the transformed point but there is +// no existing normal so we need to define the rotation and timesApplied. +void Group::MakeRevolveEndFaces(IdList *el, hEntity pt, int ai, int af) +{ + if(pt.v == 0) return; + Group *src = SK.GetGroup(opA); + Vector n = src->polyLoops.normal; + + // When there is no loop normal (e.g. if the loop is broken), use normal of workplane + // as fallback, to avoid breaking constraints depending on the faces. + if(n.Equals(Vector::From(0.0, 0.0, 0.0)) && src->type == Group::Type::DRAWING_WORKPLANE) { + n = SK.GetEntity(src->h.entity(0))->Normal()->NormalN(); + } + + Entity en = {}; + en.type = Entity::Type::FACE_ROT_NORMAL_PT; + en.group = h; + // The center of rotation + en.param[0] = h.param(0); + en.param[1] = h.param(1); + en.param[2] = h.param(2); + // The rotation quaternion + en.param[3] = h.param(3); + en.param[4] = h.param(4); + en.param[5] = h.param(5); + en.param[6] = h.param(6); + + en.numNormal = Quaternion::From(0, n.x, n.y, n.z); + en.point[0] = Remap(pt, REMAP_LATHE_START); + en.timesApplied = ai; + en.h = Remap(Entity::NO_ENTITY, REMAP_LATHE_START); + el->Add(&en); + + en.point[0] = Remap(pt, REMAP_LATHE_END); + en.timesApplied = af; + en.h = Remap(Entity::NO_ENTITY, REMAP_LATHE_END); + el->Add(&en); +} + void Group::MakeExtrusionTopBottomFaces(IdList *el, hEntity pt) { if(pt.v == 0) return; Group *src = SK.GetGroup(opA); Vector n = src->polyLoops.normal; + // When there is no loop normal (e.g. if the loop is broken), use normal of workplane + // as fallback, to avoid breaking constraints depending on the faces. + if(n.Equals(Vector::From(0.0, 0.0, 0.0)) && src->type == Group::Type::DRAWING_WORKPLANE) { + n = SK.GetEntity(src->h.entity(0))->Normal()->NormalN(); + } + Entity en = {}; - en.type = Entity::FACE_NORMAL_PT; + en.type = Entity::Type::FACE_NORMAL_PT; en.group = h; en.numNormal = Quaternion::From(0, n.x, n.y, n.z); @@ -776,8 +1021,8 @@ void Group::MakeExtrusionTopBottomFaces(IdList *el, hEntity pt) void Group::CopyEntity(IdList *el, Entity *ep, int timesApplied, int remap, hParam dx, hParam dy, hParam dz, - hParam qw, hParam qvx, hParam qvy, hParam qvz, - bool asTrans, bool asAxisAngle) + hParam qw, hParam qvx, hParam qvy, hParam qvz, hParam dist, + CopyAs as) { Entity en = {}; en.type = ep->type; @@ -789,28 +1034,34 @@ void Group::CopyEntity(IdList *el, en.style = ep->style; en.str = ep->str; en.font = ep->font; + en.file = ep->file; switch(ep->type) { - case Entity::WORKPLANE: + case Entity::Type::WORKPLANE: // Don't copy these. return; - case Entity::POINT_N_COPY: - case Entity::POINT_N_TRANS: - case Entity::POINT_N_ROT_TRANS: - case Entity::POINT_N_ROT_AA: - case Entity::POINT_IN_3D: - case Entity::POINT_IN_2D: - if(asTrans) { - en.type = Entity::POINT_N_TRANS; + case Entity::Type::POINT_N_COPY: + case Entity::Type::POINT_N_TRANS: + case Entity::Type::POINT_N_ROT_TRANS: + case Entity::Type::POINT_N_ROT_AA: + case Entity::Type::POINT_N_ROT_AXIS_TRANS: + case Entity::Type::POINT_IN_3D: + case Entity::Type::POINT_IN_2D: + if(as == CopyAs::N_TRANS) { + en.type = Entity::Type::POINT_N_TRANS; en.param[0] = dx; en.param[1] = dy; en.param[2] = dz; + } else if(as == CopyAs::NUMERIC) { + en.type = Entity::Type::POINT_N_COPY; } else { - if(asAxisAngle) { - en.type = Entity::POINT_N_ROT_AA; + if(as == CopyAs::N_ROT_AA) { + en.type = Entity::Type::POINT_N_ROT_AA; + } else if (as == CopyAs::N_ROT_AXIS_TRANS) { + en.type = Entity::Type::POINT_N_ROT_AXIS_TRANS; } else { - en.type = Entity::POINT_N_ROT_TRANS; + en.type = Entity::Type::POINT_N_ROT_TRANS; } en.param[0] = dx; en.param[1] = dy; @@ -819,22 +1070,25 @@ void Group::CopyEntity(IdList *el, en.param[4] = qvx; en.param[5] = qvy; en.param[6] = qvz; + if (as == CopyAs::N_ROT_AXIS_TRANS) { + en.param[7] = dist; + } } en.numPoint = (ep->actPoint).ScaledBy(scale); break; - case Entity::NORMAL_N_COPY: - case Entity::NORMAL_N_ROT: - case Entity::NORMAL_N_ROT_AA: - case Entity::NORMAL_IN_3D: - case Entity::NORMAL_IN_2D: - if(asTrans) { - en.type = Entity::NORMAL_N_COPY; - } else { - if(asAxisAngle) { - en.type = Entity::NORMAL_N_ROT_AA; + case Entity::Type::NORMAL_N_COPY: + case Entity::Type::NORMAL_N_ROT: + case Entity::Type::NORMAL_N_ROT_AA: + case Entity::Type::NORMAL_IN_3D: + case Entity::Type::NORMAL_IN_2D: + if(as == CopyAs::N_TRANS || as == CopyAs::NUMERIC) { + en.type = Entity::Type::NORMAL_N_COPY; + } else { // N_ROT_AXIS_TRANS probably doesn't warrant a new entity Type + if(as == CopyAs::N_ROT_AA || as == CopyAs::N_ROT_AXIS_TRANS) { + en.type = Entity::Type::NORMAL_N_ROT_AA; } else { - en.type = Entity::NORMAL_N_ROT; + en.type = Entity::Type::NORMAL_N_ROT; } en.param[0] = qw; en.param[1] = qvx; @@ -847,27 +1101,41 @@ void Group::CopyEntity(IdList *el, en.point[0] = Remap(ep->point[0], remap); break; - case Entity::DISTANCE_N_COPY: - case Entity::DISTANCE: - en.type = Entity::DISTANCE_N_COPY; + case Entity::Type::DISTANCE_N_COPY: + case Entity::Type::DISTANCE: + en.type = Entity::Type::DISTANCE_N_COPY; en.numDistance = ep->actDistance*fabs(scale); break; - case Entity::FACE_NORMAL_PT: - case Entity::FACE_XPROD: - case Entity::FACE_N_ROT_TRANS: - case Entity::FACE_N_TRANS: - case Entity::FACE_N_ROT_AA: - if(asTrans) { - en.type = Entity::FACE_N_TRANS; + case Entity::Type::FACE_NORMAL_PT: + case Entity::Type::FACE_XPROD: + case Entity::Type::FACE_N_ROT_TRANS: + case Entity::Type::FACE_N_TRANS: + case Entity::Type::FACE_N_ROT_AA: + case Entity::Type::FACE_ROT_NORMAL_PT: + case Entity::Type::FACE_N_ROT_AXIS_TRANS: + if(as == CopyAs::N_TRANS) { + en.type = Entity::Type::FACE_N_TRANS; + en.param[0] = dx; + en.param[1] = dy; + en.param[2] = dz; + } else if (as == CopyAs::NUMERIC) { + en.type = Entity::Type::FACE_NORMAL_PT; + } else if (as == CopyAs::N_ROT_AXIS_TRANS) { + en.type = Entity::Type::FACE_N_ROT_AXIS_TRANS; en.param[0] = dx; en.param[1] = dy; en.param[2] = dz; + en.param[3] = qw; + en.param[4] = qvx; + en.param[5] = qvy; + en.param[6] = qvz; + en.param[7] = dist; } else { - if(asAxisAngle) { - en.type = Entity::FACE_N_ROT_AA; + if(as == CopyAs::N_ROT_AA) { + en.type = Entity::Type::FACE_N_ROT_AA; } else { - en.type = Entity::FACE_N_ROT_TRANS; + en.type = Entity::Type::FACE_N_ROT_TRANS; } en.param[0] = dx; en.param[1] = dy; diff --git a/src/groupmesh.cpp b/src/groupmesh.cpp index 77130bc..2d509ae 100644 --- a/src/groupmesh.cpp +++ b/src/groupmesh.cpp @@ -7,8 +7,6 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -#define gs (SS.GW.gs) - void Group::AssembleLoops(bool *allClosed, bool *allCoplanar, bool *allNonZeroLen) @@ -16,13 +14,15 @@ void Group::AssembleLoops(bool *allClosed, SBezierList sbl = {}; int i; - for(i = 0; i < SK.entity.n; i++) { - Entity *e = &(SK.entity.elem[i]); - if(e->group.v != h.v) continue; - if(e->construction) continue; - if(e->forceHidden) continue; - - e->GenerateBezierCurves(&sbl); + for(auto &e : SK.entity) { + if(e.group != h) + continue; + if(e.construction) + continue; + if(e.forceHidden) + continue; + + e.GenerateBezierCurves(&sbl); } SBezier *sb; @@ -53,29 +53,29 @@ void Group::AssembleLoops(bool *allClosed, sbl.Clear(); } -void Group::GenerateLoops(void) { +void Group::GenerateLoops() { polyLoops.Clear(); bezierLoops.Clear(); bezierOpens.Clear(); - if(type == DRAWING_3D || type == DRAWING_WORKPLANE || - type == ROTATE || type == TRANSLATE || type == LINKED) + if(type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE || + type == Type::ROTATE || type == Type::TRANSLATE || type == Type::LINKED) { bool allClosed = false, allCoplanar = false, allNonZeroLen = false; AssembleLoops(&allClosed, &allCoplanar, &allNonZeroLen); if(!allNonZeroLen) { - polyError.how = POLY_ZERO_LEN_EDGE; + polyError.how = PolyError::ZERO_LEN_EDGE; } else if(!allCoplanar) { - polyError.how = POLY_NOT_COPLANAR; + polyError.how = PolyError::NOT_COPLANAR; } else if(!allClosed) { - polyError.how = POLY_NOT_CLOSED; + polyError.how = PolyError::NOT_CLOSED; } else { - polyError.how = POLY_GOOD; + polyError.how = PolyError::GOOD; // The self-intersecting check is kind of slow, so don't run it // unless requested. if(SS.checkClosedContour) { if(polyLoops.SelfIntersecting(&(polyError.errorPointAt))) { - polyError.how = POLY_SELF_INTERSECTING; + polyError.how = PolyError::SELF_INTERSECTING; } } } @@ -86,7 +86,7 @@ void SShell::RemapFaces(Group *g, int remap) { SSurface *ss; for(ss = surface.First(); ss; ss = surface.NextAfter(ss)){ hEntity face = { ss->face }; - if(face.v == Entity::NO_ENTITY.v) continue; + if(face == Entity::NO_ENTITY) continue; face = g->Remap(face, remap); ss->face = face.v; @@ -97,7 +97,7 @@ void SMesh::RemapFaces(Group *g, int remap) { STriangle *tr; for(tr = l.First(); tr; tr = l.NextAfter(tr)) { hEntity face = { tr->meta.face }; - if(face.v == Entity::NO_ENTITY.v) continue; + if(face == Entity::NO_ENTITY) continue; face = g->Remap(face, remap); tr->meta.face = face.v; @@ -105,26 +105,29 @@ void SMesh::RemapFaces(Group *g, int remap) { } template -void Group::GenerateForStepAndRepeat(T *steps, T *outs) { - T workA, workB; - workA = {}; - workB = {}; - T *soFar = &workA, *scratch = &workB; +void Group::GenerateForStepAndRepeat(T *steps, T *outs, Group::CombineAs forWhat) { int n = (int)valA, a0 = 0; - if(subtype == ONE_SIDED && skipFirst) { + if(subtype == Subtype::ONE_SIDED && skipFirst) { a0++; n++; } + int a; + // create all the transformed copies + std::vector transd(n); + std::vector workA(n); + workA[0] = {}; + // first generate a shell/mesh with each transformed copy +#pragma omp parallel for for(a = a0; a < n; a++) { - int ap = a*2 - (subtype == ONE_SIDED ? 0 : (n-1)); - int remap = (a == (n - 1)) ? REMAP_LAST : a; + transd[a] = {}; + workA[a] = {}; + int ap = a*2 - (subtype == Subtype::ONE_SIDED ? 0 : (n-1)); - T transd = {}; - if(type == TRANSLATE) { + if(type == Type::TRANSLATE) { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); trans = trans.ScaledBy(ap); - transd.MakeFromTransformationOf(steps, + transd[a].MakeFromTransformationOf(steps, trans, Quaternion::IDENTITY, 1.0); } else { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); @@ -133,31 +136,49 @@ void Group::GenerateForStepAndRepeat(T *steps, T *outs) { Vector axis = Vector::From(h.param(4), h.param(5), h.param(6)); Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z); // Rotation is centered at t; so A(x - t) + t = Ax + (t - At) - transd.MakeFromTransformationOf(steps, + transd[a].MakeFromTransformationOf(steps, trans.Minus(q.Rotate(trans)), q, 1.0); } - + } + for(a = a0; a < n; a++) { // We need to rewrite any plane face entities to the transformed ones. - transd.RemapFaces(this, remap); + int remap = (a == (n - 1)) ? REMAP_LAST : a; + transd[a].RemapFaces(this, remap); + } - // And tack this transformed copy on to the return. - if(soFar->IsEmpty()) { - scratch->MakeFromCopyOf(&transd); - } else { - scratch->MakeFromUnionOf(soFar, &transd); + std::vector *soFar = &transd; + std::vector *scratch = &workA; + // do the boolean operations on pairs of equal size + while(n > 1) { + for(a = 0; a < n; a+=2) { + scratch->at(a/2).Clear(); + // combine a pair of shells + if((a==0) && (a0==1)) { // if the first was skipped just copy the 2nd + scratch->at(a/2).MakeFromCopyOf(&(soFar->at(a+1))); + (soFar->at(a+1)).Clear(); + a0 = 0; + } else if (a == n-1) { // for an odd number just copy the last one + scratch->at(a/2).MakeFromCopyOf(&(soFar->at(a))); + (soFar->at(a)).Clear(); + } else if(forWhat == CombineAs::ASSEMBLE) { + scratch->at(a/2).MakeFromAssemblyOf(&(soFar->at(a)), &(soFar->at(a+1))); + (soFar->at(a)).Clear(); + (soFar->at(a+1)).Clear(); + } else { + scratch->at(a/2).MakeFromUnionOf(&(soFar->at(a)), &(soFar->at(a+1))); + (soFar->at(a)).Clear(); + (soFar->at(a+1)).Clear(); + } } - swap(scratch, soFar); - scratch->Clear(); - transd.Clear(); + n = (n+1)/2; } - outs->Clear(); - *outs = *soFar; + *outs = soFar->at(0); } template -void Group::GenerateForBoolean(T *prevs, T *thiss, T *outs, int how) { +void Group::GenerateForBoolean(T *prevs, T *thiss, T *outs, Group::CombineAs how) { // If this group contributes no new mesh, then our running mesh is the // same as last time, no combining required. Likewise if we have a mesh // but it's suppressed. @@ -168,16 +189,26 @@ void Group::GenerateForBoolean(T *prevs, T *thiss, T *outs, int how) { // So our group's shell appears in thisShell. Combine this with the // previous group's shell, using the requested operation. - if(how == COMBINE_AS_UNION) { - outs->MakeFromUnionOf(prevs, thiss); - } else if(how == COMBINE_AS_DIFFERENCE) { - outs->MakeFromDifferenceOf(prevs, thiss); - } else { - outs->MakeFromAssemblyOf(prevs, thiss); + switch(how) { + case CombineAs::UNION: + outs->MakeFromUnionOf(prevs, thiss); + break; + + case CombineAs::DIFFERENCE: + outs->MakeFromDifferenceOf(prevs, thiss); + break; + + case CombineAs::INTERSECTION: + outs->MakeFromIntersectionOf(prevs, thiss); + break; + + case CombineAs::ASSEMBLE: + outs->MakeFromAssemblyOf(prevs, thiss); + break; } } -void Group::GenerateShellAndMesh(void) { +void Group::GenerateShellAndMesh() { bool prevBooleanFailed = booleanFailed; booleanFailed = false; @@ -191,28 +222,34 @@ void Group::GenerateShellAndMesh(void) { // Don't attempt a lathe or extrusion unless the source section is good: // planar and not self-intersecting. bool haveSrc = true; - if(type == EXTRUDE || type == LATHE) { + if(type == Type::EXTRUDE || type == Type::LATHE || type == Type::REVOLVE) { Group *src = SK.GetGroup(opA); - if(src->polyError.how != POLY_GOOD) { + if(src->polyError.how != PolyError::GOOD) { haveSrc = false; } } - if(type == TRANSLATE || type == ROTATE) { - // A step and repeat gets merged against the group's prevous group, + if(type == Type::TRANSLATE || type == Type::ROTATE) { + // A step and repeat gets merged against the group's previous group, // not our own previous group. srcg = SK.GetGroup(opA); if(!srcg->suppress) { - GenerateForStepAndRepeat(&(srcg->thisShell), &thisShell); - GenerateForStepAndRepeat (&(srcg->thisMesh), &thisMesh); + if(!IsForcedToMesh()) { + GenerateForStepAndRepeat(&(srcg->thisShell), &thisShell, srcg->meshCombine); + } else { + SMesh prevm = {}; + prevm.MakeFromCopyOf(&srcg->thisMesh); + srcg->thisShell.TriangulateInto(&prevm); + GenerateForStepAndRepeat (&prevm, &thisMesh, srcg->meshCombine); + } } - } else if(type == EXTRUDE && haveSrc) { + } else if(type == Type::EXTRUDE && haveSrc) { Group *src = SK.GetGroup(opA); Vector translate = Vector::From(h.param(0), h.param(1), h.param(2)); Vector tbot, ttop; - if(subtype == ONE_SIDED) { + if(subtype == Subtype::ONE_SIDED) { tbot = Vector::From(0, 0, 0); ttop = translate.ScaledBy(2); } else { tbot = translate.ScaledBy(-1); ttop = translate.ScaledBy(1); @@ -229,8 +266,10 @@ void Group::GenerateShellAndMesh(void) { // that face, so that the user can select them with the mouse. Vector onOrig = sbls->point; int i; + // Not using range-for here because we're starting at a different place and using + // indices for meaning. for(i = is; i < thisShell.surface.n; i++) { - SSurface *ss = &(thisShell.surface.elem[i]); + SSurface *ss = &(thisShell.surface[i]); hEntity face = Entity::NO_ENTITY; Vector p = ss->PointAt(0, 0), @@ -255,8 +294,8 @@ void Group::GenerateShellAndMesh(void) { Entity *e; for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(e->group.v != opA.v) continue; - if(e->type != Entity::LINE_SEGMENT) continue; + if(e->group != opA) continue; + if(e->type != Entity::Type::LINE_SEGMENT) continue; Vector a = SK.GetEntity(e->point[0])->PointGetNum(), b = SK.GetEntity(e->point[1])->PointGetNum(); @@ -275,7 +314,7 @@ void Group::GenerateShellAndMesh(void) { } } } - } else if(type == LATHE && haveSrc) { + } else if(type == Type::LATHE && haveSrc) { Group *src = SK.GetGroup(opA); Vector pt = SK.GetEntity(predef.origin)->PointGetNum(), @@ -287,7 +326,52 @@ void Group::GenerateShellAndMesh(void) { for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { thisShell.MakeFromRevolutionOf(sbls, pt, axis, color, this); } - } else if(type == LINKED) { + } else if(type == Type::REVOLVE && haveSrc) { + Group *src = SK.GetGroup(opA); + double anglef = SK.GetParam(h.param(3))->val * 4; // why the 4 is needed? + double dists = 0, distf = 0; + double angles = 0.0; + if(subtype != Subtype::ONE_SIDED) { + anglef *= 0.5; + angles = -anglef; + } + Vector pt = SK.GetEntity(predef.origin)->PointGetNum(), + axis = SK.GetEntity(predef.entityB)->VectorGetNum(); + axis = axis.WithMagnitude(1); + + SBezierLoopSetSet *sblss = &(src->bezierLoops); + SBezierLoopSet *sbls; + for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { + if(fabs(anglef - angles) < 2 * PI) { + thisShell.MakeFromHelicalRevolutionOf(sbls, pt, axis, color, this, + angles, anglef, dists, distf); + } else { + thisShell.MakeFromRevolutionOf(sbls, pt, axis, color, this); + } + } + } else if(type == Type::HELIX && haveSrc) { + Group *src = SK.GetGroup(opA); + double anglef = SK.GetParam(h.param(3))->val * 4; // why the 4 is needed? + double dists = 0, distf = 0; + double angles = 0.0; + distf = SK.GetParam(h.param(7))->val * 2; // dist is applied twice + if(subtype != Subtype::ONE_SIDED) { + anglef *= 0.5; + angles = -anglef; + distf *= 0.5; + dists = -distf; + } + Vector pt = SK.GetEntity(predef.origin)->PointGetNum(), + axis = SK.GetEntity(predef.entityB)->VectorGetNum(); + axis = axis.WithMagnitude(1); + + SBezierLoopSetSet *sblss = &(src->bezierLoops); + SBezierLoopSet *sbls; + for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { + thisShell.MakeFromHelicalRevolutionOf(sbls, pt, axis, color, this, + angles, anglef, dists, distf); + } + } else if(type == Type::LINKED) { // The imported shell or mesh are copied over, with the appropriate // transformation applied. We also must remap the face entities. Vector offset = { @@ -307,7 +391,7 @@ void Group::GenerateShellAndMesh(void) { thisShell.RemapFaces(this, 0); } - if(srcg->meshCombine != COMBINE_AS_ASSEMBLE) { + if(srcg->meshCombine != CombineAs::ASSEMBLE) { thisShell.MergeCoincidentSurfaces(); } @@ -317,12 +401,12 @@ void Group::GenerateShellAndMesh(void) { Group *prevg = srcg->RunningMeshGroup(); - if(prevg->runningMesh.IsEmpty() && thisMesh.IsEmpty() && !forceToMesh) { + if(!IsForcedToMesh()) { SShell *prevs = &(prevg->runningShell); GenerateForBoolean(prevs, &thisShell, &runningShell, srcg->meshCombine); - if(srcg->meshCombine != COMBINE_AS_ASSEMBLE) { + if(srcg->meshCombine != CombineAs::ASSEMBLE) { runningShell.MergeCoincidentSurfaces(); } @@ -346,10 +430,19 @@ void Group::GenerateShellAndMesh(void) { SMesh outm = {}; GenerateForBoolean(&prevm, &thism, &outm, srcg->meshCombine); - // And make sure that the output mesh is vertex-to-vertex. - SKdNode *root = SKdNode::From(&outm); - root->SnapToMesh(&outm); - root->MakeMeshInto(&runningMesh); + // Remove degenerate triangles; if we don't, they'll get split in SnapToMesh + // in every generated group, resulting in polynomial increase in triangle count, + // and corresponding slowdown. + outm.RemoveDegenerateTriangles(); + + if(srcg->meshCombine != CombineAs::ASSEMBLE) { + // And make sure that the output mesh is vertex-to-vertex. + SKdNode *root = SKdNode::From(&outm); + root->SnapToMesh(&outm); + root->MakeMeshInto(&runningMesh); + } else { + runningMesh.MakeFromCopyOf(&outm); + } outm.Clear(); thism.Clear(); @@ -359,7 +452,7 @@ void Group::GenerateShellAndMesh(void) { displayDirty = true; } -void Group::GenerateDisplayItems(void) { +void Group::GenerateDisplayItems() { // This is potentially slow (since we've got to triangulate a shell, or // to find the emphasized edges for a mesh), so we will run it only // if its inputs have changed. @@ -379,14 +472,8 @@ void Group::GenerateDisplayItems(void) { displayMesh.Clear(); displayMesh.MakeFromCopyOf(&(pg->displayMesh)); - displayEdges.Clear(); displayOutlines.Clear(); - if(SS.GW.showEdges) { - SEdge *se; - SEdgeList *src = &(pg->displayEdges); - for(se = src->l.First(); se; se = src->l.NextAfter(se)) { - displayEdges.l.Add(se); - } + if(SS.GW.showEdges || SS.GW.showOutlines) { displayOutlines.MakeFromCopyOf(&pg->displayOutlines); } } else { @@ -404,37 +491,51 @@ void Group::GenerateDisplayItems(void) { displayMesh.AddTriangle(&trn); } - displayEdges.Clear(); displayOutlines.Clear(); - if(SS.GW.showEdges) { - if(runningMesh.l.n > 0) { + if(SS.GW.showEdges || SS.GW.showOutlines) { + SOutlineList rawOutlines = {}; + if(!runningMesh.l.IsEmpty()) { // Triangle mesh only; no shell or emphasized edges. - runningMesh.MakeCertainEdgesAndOutlinesInto( - &displayEdges, &displayOutlines, SKdNode::EMPHASIZED_EDGES); + runningMesh.MakeOutlinesInto(&rawOutlines, EdgeKind::EMPHASIZED); } else { - displayMesh.MakeCertainEdgesAndOutlinesInto( - &displayEdges, &displayOutlines, SKdNode::SHARP_EDGES); + displayMesh.MakeOutlinesInto(&rawOutlines, EdgeKind::SHARP); } + + PolylineBuilder builder; + builder.MakeFromOutlines(rawOutlines); + builder.GenerateOutlines(&displayOutlines); + rawOutlines.Clear(); } } + // If we render this mesh, we need to know whether it's transparent, + // and we'll want all transparent triangles last, to make the depth test + // work correctly. + displayMesh.PrecomputeTransparency(); + + // Recalculate mass center if needed + if(SS.centerOfMass.draw && SS.centerOfMass.dirty && h == SS.GW.activeGroup) { + SS.UpdateCenterOfMass(); + } displayDirty = false; } } -Group *Group::PreviousGroup(void) { - int i; - for(i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); - if(g->h.v == h.v) break; +Group *Group::PreviousGroup() const { + Group *prev = nullptr; + for(auto const &gh : SK.groupOrder) { + Group *g = SK.GetGroup(gh); + if(g->h == h) { + return prev; + } + prev = g; } - if(i == 0 || i >= SK.groupOrder.n) return NULL; - return SK.GetGroup(SK.groupOrder.elem[i - 1]); + return nullptr; } -Group *Group::RunningMeshGroup(void) { - if(type == TRANSLATE || type == ROTATE) { +Group *Group::RunningMeshGroup() const { + if(type == Type::TRANSLATE || type == Type::ROTATE) { return SK.GetGroup(opA)->RunningMeshGroup(); } else { return PreviousGroup(); @@ -443,175 +544,255 @@ Group *Group::RunningMeshGroup(void) { bool Group::IsMeshGroup() { switch(type) { - case Group::EXTRUDE: - case Group::LATHE: - case Group::ROTATE: - case Group::TRANSLATE: + case Group::Type::EXTRUDE: + case Group::Type::LATHE: + case Group::Type::REVOLVE: + case Group::Type::HELIX: + case Group::Type::ROTATE: + case Group::Type::TRANSLATE: return true; + + default: + return false; } - return false; } -void Group::DrawDisplayItems(int t) { - RgbaColor specColor; - bool useSpecColor; - if(t == DRAWING_3D || t == DRAWING_WORKPLANE) { - // force the color to something dim - specColor = Style::Color(Style::DIM_SOLID); - useSpecColor = true; - } else { - useSpecColor = false; // use the model color - } - // The back faces are drawn in red; should never seem them, since we - // draw closed shells, so that's a debugging aid. - GLfloat mpb[] = { 1.0f, 0.1f, 0.1f, 1.0f }; - glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, mpb); - - // When we fill the mesh, we need to know which triangles are selected - // or hovered, in order to draw them differently. - uint32_t mh = 0, ms1 = 0, ms2 = 0; - hEntity he = SS.GW.hover.entity; - if(he.v != 0 && SK.GetEntity(he)->IsFace()) { - mh = he.v; - } - SS.GW.GroupSelection(); - if(gs.faces > 0) ms1 = gs.face[0].v; - if(gs.faces > 1) ms2 = gs.face[1].v; - - if(SS.GW.showShaded || SS.GW.showHdnLines) { - if(SS.drawBackFaces && !displayMesh.isTransparent) { - // For debugging, draw the backs of the triangles in red, so that we - // notice when a shell is open - glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1); - } else { - glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0); - } +void Group::DrawMesh(DrawMeshAs how, Canvas *canvas) { + if(!(SS.GW.showShaded || + SS.GW.drawOccludedAs != GraphicsWindow::DrawOccludedAs::VISIBLE)) return; + + switch(how) { + case DrawMeshAs::DEFAULT: { + // Force the shade color to something dim to not distract from + // the sketch. + Canvas::Fill fillFront = {}; + if(!SS.GW.showShaded) { + fillFront.layer = Canvas::Layer::DEPTH_ONLY; + } + if(type == Type::DRAWING_3D || type == Type::DRAWING_WORKPLANE) { + fillFront.color = Style::Color(Style::DIM_SOLID); + } + Canvas::hFill hcfFront = canvas->GetFill(fillFront); + + // The back faces are drawn in red; should never seem them, since we + // draw closed shells, so that's a debugging aid. + Canvas::hFill hcfBack = {}; + if(SS.drawBackFaces && !displayMesh.isTransparent) { + Canvas::Fill fillBack = {}; + fillBack.layer = fillFront.layer; + fillBack.color = RgbaColor::FromFloat(1.0f, 0.1f, 0.1f); + hcfBack = canvas->GetFill(fillBack); + } else { + hcfBack = hcfFront; + } - // Draw the shaded solid into the depth buffer for hidden line removal, - // and if we're actually going to display it, to the color buffer too. - glEnable(GL_LIGHTING); - if(!SS.GW.showShaded) glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - ssglFillMesh(useSpecColor, specColor, &displayMesh, mh, ms1, ms2); - if(!SS.GW.showShaded) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glDisable(GL_LIGHTING); - } + // Draw the shaded solid into the depth buffer for hidden line removal, + // and if we're actually going to display it, to the color buffer too. + canvas->DrawMesh(displayMesh, hcfFront, hcfBack); + + // Draw mesh edges, for debugging. + if(SS.GW.showMesh) { + Canvas::Stroke strokeTriangle = {}; + strokeTriangle.zIndex = 1; + strokeTriangle.color = RgbaColor::FromFloat(0.0f, 1.0f, 0.0f); + strokeTriangle.width = 1; + strokeTriangle.unit = Canvas::Unit::PX; + Canvas::hStroke hcsTriangle = canvas->GetStroke(strokeTriangle); + SEdgeList edges = {}; + for(const STriangle &t : displayMesh.l) { + edges.AddEdge(t.a, t.b); + edges.AddEdge(t.b, t.c); + edges.AddEdge(t.c, t.a); + } + canvas->DrawEdges(edges, hcsTriangle); + edges.Clear(); + } + break; + } - if(SS.GW.showEdges) { - Vector projDir = SS.GW.projRight.Cross(SS.GW.projUp); - - glDepthMask(GL_FALSE); - if(SS.GW.showHdnLines) { - ssglDepthRangeOffset(0); - glDepthFunc(GL_GREATER); - ssglDrawEdges(&displayEdges, false, { Style::HIDDEN_EDGE }); - ssglDrawOutlines(&displayOutlines, projDir, { Style::HIDDEN_EDGE }); - glDepthFunc(GL_LEQUAL); + case DrawMeshAs::HOVERED: { + Canvas::Fill fill = {}; + fill.color = Style::Color(Style::HOVERED); + fill.pattern = Canvas::FillPattern::CHECKERED_A; + fill.zIndex = 2; + Canvas::hFill hcf = canvas->GetFill(fill); + + std::vector faces; + hEntity he = SS.GW.hover.entity; + if(he.v != 0 && SK.GetEntity(he)->IsFace()) { + faces.push_back(he.v); + } + canvas->DrawFaces(displayMesh, faces, hcf); + break; } - ssglDepthRangeOffset(2); - ssglDrawEdges(&displayEdges, false, { Style::SOLID_EDGE }); - if(SS.GW.showOutlines) { - ssglDrawOutlines(&displayOutlines, projDir, { Style::OUTLINE }); - } else { - ssglDrawOutlines(&displayOutlines, projDir, { Style::SOLID_EDGE }); + + case DrawMeshAs::SELECTED: { + Canvas::Fill fill = {}; + fill.color = Style::Color(Style::SELECTED); + fill.pattern = Canvas::FillPattern::CHECKERED_B; + fill.zIndex = 1; + Canvas::hFill hcf = canvas->GetFill(fill); + + std::vector faces; + SS.GW.GroupSelection(); + auto const &gs = SS.GW.gs; + if(gs.faces > 0) faces.push_back(gs.face[0].v); + if(gs.faces > 1) faces.push_back(gs.face[1].v); + canvas->DrawFaces(displayMesh, faces, hcf); + break; } - glDepthMask(GL_TRUE); } - - if(SS.GW.showMesh) ssglDebugMesh(&displayMesh); } -void Group::Draw(void) { +void Group::Draw(Canvas *canvas) { // Everything here gets drawn whether or not the group is hidden; we // can control this stuff independently, with show/hide solids, edges, // mesh, etc. GenerateDisplayItems(); - DrawDisplayItems(type); + DrawMesh(DrawMeshAs::DEFAULT, canvas); + + if(SS.GW.showEdges) { + Canvas::Stroke strokeEdge = Style::Stroke(Style::SOLID_EDGE); + strokeEdge.zIndex = 1; + Canvas::hStroke hcsEdge = canvas->GetStroke(strokeEdge); + + canvas->DrawOutlines(displayOutlines, hcsEdge, + SS.GW.showOutlines + ? Canvas::DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR + : Canvas::DrawOutlinesAs::EMPHASIZED_AND_CONTOUR); + + if(SS.GW.drawOccludedAs != GraphicsWindow::DrawOccludedAs::INVISIBLE) { + Canvas::Stroke strokeHidden = Style::Stroke(Style::HIDDEN_EDGE); + if(SS.GW.drawOccludedAs == GraphicsWindow::DrawOccludedAs::VISIBLE) { + strokeHidden.stipplePattern = StipplePattern::CONTINUOUS; + } + strokeHidden.layer = Canvas::Layer::OCCLUDED; + Canvas::hStroke hcsHidden = canvas->GetStroke(strokeHidden); + + canvas->DrawOutlines(displayOutlines, hcsHidden, + Canvas::DrawOutlinesAs::EMPHASIZED_AND_CONTOUR); + } + } + + if(SS.GW.showOutlines) { + Canvas::Stroke strokeOutline = Style::Stroke(Style::OUTLINE); + strokeOutline.zIndex = 1; + Canvas::hStroke hcsOutline = canvas->GetStroke(strokeOutline); + + canvas->DrawOutlines(displayOutlines, hcsOutline, + Canvas::DrawOutlinesAs::CONTOUR_ONLY); + } +} + +void Group::DrawPolyError(Canvas *canvas) { + const Camera &camera = canvas->GetCamera(); + + Canvas::Stroke strokeUnclosed = Style::Stroke(Style::DRAW_ERROR); + strokeUnclosed.color = strokeUnclosed.color.WithAlpha(50); + Canvas::hStroke hcsUnclosed = canvas->GetStroke(strokeUnclosed); - if(!SS.checkClosedContour) return; + Canvas::Stroke strokeError = Style::Stroke(Style::DRAW_ERROR); + strokeError.layer = Canvas::Layer::FRONT; + strokeError.width = 1.0f; + Canvas::hStroke hcsError = canvas->GetStroke(strokeError); + + double textHeight = Style::DefaultTextHeight() / camera.scale; // And finally show the polygons too, and any errors if it's not possible // to assemble the lines into closed polygons. - if(polyError.how == POLY_NOT_CLOSED) { + if(polyError.how == PolyError::NOT_CLOSED) { // Report this error only in sketch-in-workplane groups; otherwise // it's just a nuisance. - if(type == DRAWING_WORKPLANE) { - glDisable(GL_DEPTH_TEST); - ssglColorRGBa(Style::Color(Style::DRAW_ERROR), 0.2); - ssglLineWidth (Style::Width(Style::DRAW_ERROR)); - glBegin(GL_LINES); - ssglVertex3v(polyError.notClosedAt.a); - ssglVertex3v(polyError.notClosedAt.b); - glEnd(); - ssglColorRGB(Style::Color(Style::DRAW_ERROR)); - ssglWriteText("not closed contour, or not all same style!", - Style::DefaultTextHeight(), - polyError.notClosedAt.b, SS.GW.projRight, SS.GW.projUp, - NULL, NULL); - glEnable(GL_DEPTH_TEST); + if(type == Type::DRAWING_WORKPLANE) { + canvas->DrawVectorText(_("not closed contour, or not all same style!"), + textHeight, + polyError.notClosedAt.b, camera.projRight, camera.projUp, + hcsError); + canvas->DrawLine(polyError.notClosedAt.a, polyError.notClosedAt.b, hcsUnclosed); } - } else if(polyError.how == POLY_NOT_COPLANAR || - polyError.how == POLY_SELF_INTERSECTING || - polyError.how == POLY_ZERO_LEN_EDGE) - { + } else if(polyError.how == PolyError::NOT_COPLANAR || + polyError.how == PolyError::SELF_INTERSECTING || + polyError.how == PolyError::ZERO_LEN_EDGE) { // These errors occur at points, not lines - if(type == DRAWING_WORKPLANE) { - glDisable(GL_DEPTH_TEST); - ssglColorRGB(Style::Color(Style::DRAW_ERROR)); + if(type == Type::DRAWING_WORKPLANE) { const char *msg; - if(polyError.how == POLY_NOT_COPLANAR) { - msg = "points not all coplanar!"; - } else if(polyError.how == POLY_SELF_INTERSECTING) { - msg = "contour is self-intersecting!"; + if(polyError.how == PolyError::NOT_COPLANAR) { + msg = _("points not all coplanar!"); + } else if(polyError.how == PolyError::SELF_INTERSECTING) { + msg = _("contour is self-intersecting!"); } else { - msg = "zero-length edge!"; + msg = _("zero-length edge!"); } - ssglWriteText(msg, Style::DefaultTextHeight(), - polyError.errorPointAt, SS.GW.projRight, SS.GW.projUp, - NULL, NULL); - glEnable(GL_DEPTH_TEST); + canvas->DrawVectorText(msg, textHeight, + polyError.errorPointAt, camera.projRight, camera.projUp, + hcsError); } } else { // The contours will get filled in DrawFilledPaths. } } -void Group::FillLoopSetAsPolygon(SBezierLoopSet *sbls) { - SPolygon sp = {}; - sbls->MakePwlInto(&sp); - ssglDepthRangeOffset(1); - ssglFillPolygon(&sp); - ssglDepthRangeOffset(0); - sp.Clear(); -} +void Group::DrawFilledPaths(Canvas *canvas) { + for(const SBezierLoopSet &sbls : bezierLoops.l) { + if(sbls.l.IsEmpty() || sbls.l[0].l.IsEmpty()) + continue; -void Group::DrawFilledPaths(void) { - SBezierLoopSet *sbls; - SBezierLoopSetSet *sblss = &bezierLoops; - for(sbls = sblss->l.First(); sbls; sbls = sblss->l.NextAfter(sbls)) { - if(sbls->l.n == 0 || sbls->l.elem[0].l.n == 0) continue; // In an assembled loop, all the styles should be the same; so doesn't // matter which one we grab. - SBezier *sb = &(sbls->l.elem[0].l.elem[0]); - hStyle hs = { (uint32_t)sb->auxA }; - Style *s = Style::Get(hs); + const SBezier *sb = &(sbls.l[0].l[0]); + Style *s = Style::Get({ (uint32_t)sb->auxA }); + + Canvas::Fill fill = {}; + fill.zIndex = 1; if(s->filled) { // This is a filled loop, where the user specified a fill color. - ssglColorRGBa(s->fillColor, 1); - FillLoopSetAsPolygon(sbls); - } else { - if(h.v == SS.GW.activeGroup.v && SS.checkClosedContour && - polyError.how == POLY_GOOD) - { - // If this is the active group, and we are supposed to check - // for closed contours, and we do indeed have a closed and - // non-intersecting contour, then fill it dimly. - ssglColorRGBa(Style::Color(Style::CONTOUR_FILL), 0.5); - ssglDepthRangeOffset(1); - FillLoopSetAsPolygon(sbls); - ssglDepthRangeOffset(0); - } - } + fill.color = s->fillColor; + } else if(h == SS.GW.activeGroup && SS.checkClosedContour && + polyError.how == PolyError::GOOD) { + // If this is the active group, and we are supposed to check + // for closed contours, and we do indeed have a closed and + // non-intersecting contour, then fill it dimly. + fill.color = Style::Color(Style::CONTOUR_FILL).WithAlpha(127); + } else continue; + Canvas::hFill hcf = canvas->GetFill(fill); + + SPolygon sp = {}; + sbls.MakePwlInto(&sp); + canvas->DrawPolygon(sp, hcf); + sp.Clear(); + } +} + +void Group::DrawContourAreaLabels(Canvas *canvas) { + const Camera &camera = canvas->GetCamera(); + Vector gr = camera.projRight.ScaledBy(1 / camera.scale); + Vector gu = camera.projUp.ScaledBy(1 / camera.scale); + + for(SBezierLoopSet &sbls : bezierLoops.l) { + if(sbls.l.IsEmpty() || sbls.l[0].l.IsEmpty()) + continue; + + Vector min = sbls.l[0].l[0].ctrl[0]; + Vector max = min; + Vector zero = Vector::From(0.0, 0.0, 0.0); + sbls.GetBoundingProjd(Vector::From(1.0, 0.0, 0.0), zero, &min.x, &max.x); + sbls.GetBoundingProjd(Vector::From(0.0, 1.0, 0.0), zero, &min.y, &max.y); + sbls.GetBoundingProjd(Vector::From(0.0, 0.0, 1.0), zero, &min.z, &max.z); + + Vector mid = min.Plus(max).ScaledBy(0.5); + + hStyle hs = { Style::CONSTRAINT }; + Canvas::Stroke stroke = Style::Stroke(hs); + stroke.layer = Canvas::Layer::FRONT; + + std::string label = SS.MmToStringSI(fabs(sbls.SignedArea()), /*dim=*/2); + double fontHeight = Style::TextHeight(hs); + double textWidth = VectorFont::Builtin()->GetWidth(fontHeight, label), + textHeight = VectorFont::Builtin()->GetCapHeight(fontHeight); + Vector pos = mid.Minus(gr.ScaledBy(textWidth / 2.0)) + .Minus(gu.ScaledBy(textHeight / 2.0)); + canvas->DrawVectorText(label, fontHeight, pos, gr, gu, canvas->GetStroke(stroke)); } } diff --git a/src/gtk/gtkmain.cpp b/src/gtk/gtkmain.cpp deleted file mode 100644 index 386e970..0000000 --- a/src/gtk/gtkmain.cpp +++ /dev/null @@ -1,1611 +0,0 @@ -//----------------------------------------------------------------------------- -// Our main() function, and GTK3-specific stuff to set up our windows and -// otherwise handle our interface to the operating system. Everything -// outside gtk/... should be standard C++ and OpenGL. -// -// Copyright 2015 -//----------------------------------------------------------------------------- -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if HAVE_GTK3 -#include -#else -#include -#endif - -#include -#include -#include -#include - -#include - -#include "solvespace.h" -#include "config.h" -#include "../unix/gloffscreen.h" - -#ifdef HAVE_SPACEWARE -#include -#endif - -namespace SolveSpace { -/* Settings */ - -/* Why not just use GSettings? Two reasons. It doesn't allow to easily see - whether the setting had the default value, and it requires to install - a schema globally. */ -static json_object *settings = NULL; - -static std::string CnfPrepare() { - // Refer to http://standards.freedesktop.org/basedir-spec/latest/ - - std::string dir; - if(getenv("XDG_CONFIG_HOME")) { - dir = std::string(getenv("XDG_CONFIG_HOME")) + "/solvespace"; - } else if(getenv("HOME")) { - dir = std::string(getenv("HOME")) + "/.config/solvespace"; - } else { - dbp("neither XDG_CONFIG_HOME nor HOME are set"); - return ""; - } - - struct stat st; - if(stat(dir.c_str(), &st)) { - if(errno == ENOENT) { - if(mkdir(dir.c_str(), 0777)) { - dbp("cannot mkdir %s: %s", dir.c_str(), strerror(errno)); - return ""; - } - } else { - dbp("cannot stat %s: %s", dir.c_str(), strerror(errno)); - return ""; - } - } else if(!S_ISDIR(st.st_mode)) { - dbp("%s is not a directory", dir.c_str()); - return ""; - } - - return dir + "/settings.json"; -} - -static void CnfLoad() { - std::string path = CnfPrepare(); - if(path.empty()) - return; - - if(settings) - json_object_put(settings); // deallocate - - settings = json_object_from_file(path.c_str()); - if(!settings) { - if(errno != ENOENT) - dbp("cannot load settings: %s", strerror(errno)); - - settings = json_object_new_object(); - } -} - -static void CnfSave() { - std::string path = CnfPrepare(); - if(path.empty()) - return; - - /* json-c <0.12 has the first argument non-const here */ - if(json_object_to_file_ext((char*) path.c_str(), settings, JSON_C_TO_STRING_PRETTY)) - dbp("cannot save settings: %s", strerror(errno)); -} - -void CnfFreezeInt(uint32_t val, const std::string &key) { - struct json_object *jval = json_object_new_int(val); - json_object_object_add(settings, key.c_str(), jval); - CnfSave(); -} - -uint32_t CnfThawInt(uint32_t val, const std::string &key) { - struct json_object *jval; - if(json_object_object_get_ex(settings, key.c_str(), &jval)) - return json_object_get_int(jval); - else return val; -} - -void CnfFreezeFloat(float val, const std::string &key) { - struct json_object *jval = json_object_new_double(val); - json_object_object_add(settings, key.c_str(), jval); - CnfSave(); -} - -float CnfThawFloat(float val, const std::string &key) { - struct json_object *jval; - if(json_object_object_get_ex(settings, key.c_str(), &jval)) - return json_object_get_double(jval); - else return val; -} - -void CnfFreezeString(const std::string &val, const std::string &key) { - struct json_object *jval = json_object_new_string(val.c_str()); - json_object_object_add(settings, key.c_str(), jval); - CnfSave(); -} - -std::string CnfThawString(const std::string &val, const std::string &key) { - struct json_object *jval; - if(json_object_object_get_ex(settings, key.c_str(), &jval)) - return json_object_get_string(jval); - return val; -} - -static void CnfFreezeWindowPos(Gtk::Window *win, const std::string &key) { - int x, y, w, h; - win->get_position(x, y); - win->get_size(w, h); - - CnfFreezeInt(x, key + "_left"); - CnfFreezeInt(y, key + "_top"); - CnfFreezeInt(w, key + "_width"); - CnfFreezeInt(h, key + "_height"); -} - -static void CnfThawWindowPos(Gtk::Window *win, const std::string &key) { - int x, y, w, h; - win->get_position(x, y); - win->get_size(w, h); - - x = CnfThawInt(x, key + "_left"); - y = CnfThawInt(y, key + "_top"); - w = CnfThawInt(w, key + "_width"); - h = CnfThawInt(h, key + "_height"); - - win->move(x, y); - win->resize(w, h); -} - -/* Timers */ - -int64_t GetMilliseconds(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return 1000 * (uint64_t) ts.tv_sec + ts.tv_nsec / 1000000; -} - -static bool TimerCallback() { - SS.GW.TimerCallback(); - SS.TW.TimerCallback(); - return false; -} - -void SetTimerFor(int milliseconds) { - Glib::signal_timeout().connect(&TimerCallback, milliseconds); -} - -static bool AutosaveTimerCallback() { - SS.Autosave(); - return false; -} - -void SetAutosaveTimerFor(int minutes) { - Glib::signal_timeout().connect(&AutosaveTimerCallback, minutes * 60 * 1000); -} - -static bool LaterCallback() { - SS.DoLater(); - return false; -} - -void ScheduleLater() { - Glib::signal_idle().connect(&LaterCallback); -} - -/* GL wrapper */ - -#define GL_CHECK() \ - do { \ - int err = (int)glGetError(); \ - if(err) dbp("%s:%d: glGetError() == 0x%X %s", \ - __FILE__, __LINE__, err, gluErrorString(err)); \ - } while (0) - -class GlWidget : public Gtk::DrawingArea { -public: - GlWidget() : _offscreen(NULL) { - _xdisplay = gdk_x11_get_default_xdisplay(); - - int glxmajor, glxminor; - if(!glXQueryVersion(_xdisplay, &glxmajor, &glxminor)) { - dbp("OpenGL is not supported"); - oops(); - } - - if(glxmajor < 1 || (glxmajor == 1 && glxminor < 3)) { - dbp("GLX version %d.%d is too old; 1.3 required", glxmajor, glxminor); - oops(); - } - - static int fbconfig_attrs[] = { - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_RED_SIZE, 8, - GLX_GREEN_SIZE, 8, - GLX_BLUE_SIZE, 8, - GLX_DEPTH_SIZE, 24, - None - }; - int fbconfig_num = 0; - GLXFBConfig *fbconfigs = glXChooseFBConfig(_xdisplay, DefaultScreen(_xdisplay), - fbconfig_attrs, &fbconfig_num); - if(!fbconfigs || fbconfig_num == 0) - oops(); - - /* prefer FBConfigs with depth of 32; - * Mesa software rasterizer explodes with a BadMatch without this; - * without this, Intel on Mesa flickers horribly for some reason. - this does not seem to affect other rasterizers (ie NVidia). - - see this Mesa bug: - http://lists.freedesktop.org/archives/mesa-dev/2015-January/074693.html */ - GLXFBConfig fbconfig = fbconfigs[0]; - for(int i = 0; i < fbconfig_num; i++) { - XVisualInfo *visual_info = glXGetVisualFromFBConfig(_xdisplay, fbconfigs[i]); - /* some GL visuals, notably on Chromium GL, do not have an associated - X visual; this is not an obstacle as we always render offscreen. */ - if(!visual_info) continue; - int depth = visual_info->depth; - XFree(visual_info); - - if(depth == 32) { - fbconfig = fbconfigs[i]; - break; - } - } - - _glcontext = glXCreateNewContext(_xdisplay, - fbconfig, GLX_RGBA_TYPE, 0, True); - if(!_glcontext) { - dbp("cannot create OpenGL context"); - oops(); - } - - XFree(fbconfigs); - - /* create a dummy X window to create a rendering context against. - we could use a Pbuffer, but some implementations (Chromium GL) - don't support these. we could use an existing window, but - some implementations (Chromium GL... do you see a pattern?) - do really strange things, i.e. draw a black rectangle on - the very front of the desktop if you do this. */ - _xwindow = XCreateSimpleWindow(_xdisplay, - XRootWindow(_xdisplay, gdk_x11_get_default_screen()), - /*x*/ 0, /*y*/ 0, /*width*/ 1, /*height*/ 1, - /*border_width*/ 0, /*border*/ 0, /*background*/ 0); - } - - ~GlWidget() { - glXMakeCurrent(_xdisplay, None, NULL); - - XDestroyWindow(_xdisplay, _xwindow); - - delete _offscreen; - - glXDestroyContext(_xdisplay, _glcontext); - } - -protected: - /* Draw on a GLX framebuffer object, then read pixels out and draw them on - the Cairo context. Slower, but you get to overlay nice widgets. */ - virtual bool on_draw(const Cairo::RefPtr &cr) { - if(!glXMakeCurrent(_xdisplay, _xwindow, _glcontext)) - oops(); - - if(!_offscreen) - _offscreen = new GLOffscreen; - - Gdk::Rectangle allocation = get_allocation(); - if(!_offscreen->begin(allocation.get_width(), allocation.get_height())) - oops(); - - on_gl_draw(); - glFlush(); - GL_CHECK(); - - Cairo::RefPtr surface = Cairo::ImageSurface::create( - _offscreen->end(), Cairo::FORMAT_RGB24, - allocation.get_width(), allocation.get_height(), allocation.get_width() * 4); - cr->set_source(surface, 0, 0); - cr->paint(); - surface->finish(); - - return true; - } - -#ifdef HAVE_GTK2 - virtual bool on_expose_event(GdkEventExpose *) { - return on_draw(get_window()->create_cairo_context()); - } -#endif - - virtual void on_gl_draw() = 0; - -private: - Display *_xdisplay; - GLXContext _glcontext; - GLOffscreen *_offscreen; - ::Window _xwindow; -}; - -/* Editor overlay */ - -class EditorOverlay : public Gtk::Fixed { -public: - EditorOverlay(Gtk::Widget &underlay) : _underlay(underlay) { - set_size_request(0, 0); - - add(_underlay); - - _entry.set_no_show_all(true); - _entry.set_has_frame(false); - add(_entry); - - _entry.signal_activate(). - connect(sigc::mem_fun(this, &EditorOverlay::on_activate)); - } - - void start_editing(int x, int y, int font_height, bool is_monospace, int minWidthChars, - const std::string &val) { - Pango::FontDescription font_desc; - font_desc.set_family(is_monospace ? "monospace" : "normal"); - font_desc.set_absolute_size(font_height * Pango::SCALE); - -#ifdef HAVE_GTK3 - /* For some reason override_font doesn't take screen DPI into - account on GTK3 when working with font descriptors specified - in absolute sizes; modify_font does on GTK2. */ - Pango::FontDescription override_font_desc(font_desc); - double dpi = get_screen()->get_resolution(); - override_font_desc.set_size(font_height * 72.0 / dpi * Pango::SCALE); - _entry.override_font(override_font_desc); -#else - _entry.modify_font(font_desc); -#endif - - /* y coordinate denotes baseline */ - Pango::FontMetrics font_metrics = get_pango_context()->get_metrics(font_desc); - y -= font_metrics.get_ascent() / Pango::SCALE; - - Glib::RefPtr layout = Pango::Layout::create(get_pango_context()); - layout->set_font_description(font_desc); - layout->set_text(val + " "); /* avoid scrolling */ - int width = layout->get_logical_extents().get_width(); - -#ifdef HAVE_GTK3 - Gtk::Border border = _entry.get_style_context()->get_padding(); - move(_entry, x - border.get_left(), y - border.get_top()); - _entry.set_width_chars(minWidthChars); - _entry.set_size_request(width / Pango::SCALE, -1); -#else - /* We need _gtk_entry_effective_inner_border, but it's not - in the public API, so emulate its logic. */ - Gtk::Border border = { 2, 2, 2, 2 }, *style_border; - gtk_widget_style_get(GTK_WIDGET(_entry.gobj()), "inner-border", - &style_border, NULL); - if(style_border) border = *style_border; - move(_entry, x - border.left, y - border.top); - /* This is what set_width_chars does. */ - int minWidth = minWidthChars * std::max(font_metrics.get_approximate_digit_width(), - font_metrics.get_approximate_char_width()); - _entry.set_size_request(std::max(width, minWidth) / Pango::SCALE, -1); -#endif - - _entry.set_text(val); - if(!_entry.is_visible()) { - _entry.show(); - _entry.grab_focus(); - _entry.add_modal_grab(); - } - } - - void stop_editing() { - if(_entry.is_visible()) - _entry.remove_modal_grab(); - _entry.hide(); - } - - bool is_editing() const { - return _entry.is_visible(); - } - - sigc::signal signal_editing_done() { - return _signal_editing_done; - } - - Gtk::Entry &get_entry() { - return _entry; - } - -protected: - virtual bool on_key_press_event(GdkEventKey *event) { - if(event->keyval == GDK_KEY_Escape) { - stop_editing(); - return true; - } - - return false; - } - - virtual void on_size_allocate(Gtk::Allocation& allocation) { - Gtk::Fixed::on_size_allocate(allocation); - - _underlay.size_allocate(allocation); - } - - virtual void on_activate() { - _signal_editing_done(_entry.get_text()); - } - -private: - Gtk::Widget &_underlay; - Gtk::Entry _entry; - sigc::signal _signal_editing_done; -}; - -/* Graphics window */ - -int DeltaYOfScrollEvent(GdkEventScroll *event) { -#ifdef HAVE_GTK3 - int delta_y = event->delta_y; -#else - int delta_y = 0; -#endif - if(delta_y == 0) { - switch(event->direction) { - case GDK_SCROLL_UP: - delta_y = -1; - break; - - case GDK_SCROLL_DOWN: - delta_y = 1; - break; - - default: - /* do nothing */ - return false; - } - } - - return delta_y; -} - -class GraphicsWidget : public GlWidget { -public: - GraphicsWidget() { - set_events(Gdk::POINTER_MOTION_MASK | - Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::BUTTON_MOTION_MASK | - Gdk::SCROLL_MASK | - Gdk::LEAVE_NOTIFY_MASK); - set_double_buffered(true); - } - -protected: - virtual bool on_configure_event(GdkEventConfigure *event) { - _w = event->width; - _h = event->height; - - return GlWidget::on_configure_event(event);; - } - - virtual void on_gl_draw() { - SS.GW.Paint(); - } - - virtual bool on_motion_notify_event(GdkEventMotion *event) { - int x, y; - ij_to_xy(event->x, event->y, x, y); - - SS.GW.MouseMoved(x, y, - event->state & GDK_BUTTON1_MASK, - event->state & GDK_BUTTON2_MASK, - event->state & GDK_BUTTON3_MASK, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK); - - return true; - } - - virtual bool on_button_press_event(GdkEventButton *event) { - int x, y; - ij_to_xy(event->x, event->y, x, y); - - switch(event->button) { - case 1: - if(event->type == GDK_BUTTON_PRESS) - SS.GW.MouseLeftDown(x, y); - else if(event->type == GDK_2BUTTON_PRESS) - SS.GW.MouseLeftDoubleClick(x, y); - break; - - case 2: - case 3: - SS.GW.MouseMiddleOrRightDown(x, y); - break; - } - - return true; - } - - virtual bool on_button_release_event(GdkEventButton *event) { - int x, y; - ij_to_xy(event->x, event->y, x, y); - - switch(event->button) { - case 1: - SS.GW.MouseLeftUp(x, y); - break; - - case 3: - SS.GW.MouseRightUp(x, y); - break; - } - - return true; - } - - virtual bool on_scroll_event(GdkEventScroll *event) { - int x, y; - ij_to_xy(event->x, event->y, x, y); - - SS.GW.MouseScroll(x, y, -DeltaYOfScrollEvent(event)); - - return true; - } - - virtual bool on_leave_notify_event (GdkEventCrossing *) { - SS.GW.MouseLeave(); - - return true; - } - -private: - int _w, _h; - void ij_to_xy(int i, int j, int &x, int &y) { - // Convert to xy (vs. ij) style coordinates, - // with (0, 0) at center - x = i - _w / 2; - y = _h / 2 - j; - } -}; - -class GraphicsWindowGtk : public Gtk::Window { -public: - GraphicsWindowGtk() : _overlay(_widget), _is_fullscreen(false) { - set_default_size(900, 600); - - _box.pack_start(_menubar, false, true); - _box.pack_start(_overlay, true, true); - - add(_box); - - _overlay.signal_editing_done(). - connect(sigc::mem_fun(this, &GraphicsWindowGtk::on_editing_done)); - } - - GraphicsWidget &get_widget() { - return _widget; - } - - EditorOverlay &get_overlay() { - return _overlay; - } - - Gtk::MenuBar &get_menubar() { - return _menubar; - } - - bool is_fullscreen() const { - return _is_fullscreen; - } - - bool emulate_key_press(GdkEventKey *event) { - return on_key_press_event(event); - } - -protected: - virtual void on_show() { - Gtk::Window::on_show(); - - CnfThawWindowPos(this, "GraphicsWindow"); - } - - virtual void on_hide() { - CnfFreezeWindowPos(this, "GraphicsWindow"); - - Gtk::Window::on_hide(); - } - - virtual bool on_delete_event(GdkEventAny *) { - if(!SS.OkayToStartNewFile()) return true; - SS.Exit(); - - return true; - } - - virtual bool on_window_state_event(GdkEventWindowState *event) { - _is_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN; - - /* The event arrives too late for the caller of ToggleFullScreen - to notice state change; and it's possible that the WM will - refuse our request, so we can't just toggle the saved state */ - SS.GW.EnsureValidActives(); - - return Gtk::Window::on_window_state_event(event); - } - - virtual bool on_key_press_event(GdkEventKey *event) { - int chr; - - switch(event->keyval) { - case GDK_KEY_Escape: - chr = GraphicsWindow::ESCAPE_KEY; - break; - - case GDK_KEY_Delete: - chr = GraphicsWindow::DELETE_KEY; - break; - - case GDK_KEY_Tab: - chr = '\t'; - break; - - case GDK_KEY_BackSpace: - case GDK_KEY_Back: - chr = '\b'; - break; - - default: - if(event->keyval >= GDK_KEY_F1 && event->keyval <= GDK_KEY_F12) { - chr = GraphicsWindow::FUNCTION_KEY_BASE + (event->keyval - GDK_KEY_F1); - } else { - chr = gdk_keyval_to_unicode(event->keyval); - } - } - - if(event->state & GDK_SHIFT_MASK){ - chr |= GraphicsWindow::SHIFT_MASK; - } - if(event->state & GDK_CONTROL_MASK) { - chr |= GraphicsWindow::CTRL_MASK; - } - - if(chr && SS.GW.KeyDown(chr)) { - return true; - } - - if(chr == '\t') { - // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=123994. - GraphicsWindow::MenuView(GraphicsWindow::MNU_SHOW_TEXT_WND); - return true; - } - - return Gtk::Window::on_key_press_event(event); - } - - virtual void on_editing_done(Glib::ustring value) { - SS.GW.EditControlDone(value.c_str()); - } - -private: - GraphicsWidget _widget; - EditorOverlay _overlay; - Gtk::MenuBar _menubar; - Gtk::VBox _box; - - bool _is_fullscreen; -}; - -std::unique_ptr GW; - -void GetGraphicsWindowSize(int *w, int *h) { - Gdk::Rectangle allocation = GW->get_widget().get_allocation(); - *w = allocation.get_width(); - *h = allocation.get_height(); -} - -void InvalidateGraphics(void) { - GW->get_widget().queue_draw(); -} - -void PaintGraphics(void) { - GW->get_widget().queue_draw(); - /* Process animation */ - Glib::MainContext::get_default()->iteration(false); -} - -void SetCurrentFilename(const std::string &filename) { - if(!filename.empty()) { - GW->set_title("SolveSpace - " + filename); - } else { - GW->set_title("SolveSpace - (not yet saved)"); - } -} - -void ToggleFullScreen(void) { - if(GW->is_fullscreen()) - GW->unfullscreen(); - else - GW->fullscreen(); -} - -bool FullScreenIsActive(void) { - return GW->is_fullscreen(); -} - -void ShowGraphicsEditControl(int x, int y, int fontHeight, int minWidthChars, - const std::string &val) { - Gdk::Rectangle rect = GW->get_widget().get_allocation(); - - // Convert to ij (vs. xy) style coordinates, - // and compensate for the input widget height due to inverse coord - int i, j; - i = x + rect.get_width() / 2; - j = -y + rect.get_height() / 2; - - GW->get_overlay().start_editing(i, j, fontHeight, /*is_monospace=*/false, minWidthChars, val); -} - -void HideGraphicsEditControl(void) { - GW->get_overlay().stop_editing(); -} - -bool GraphicsEditControlIsVisible(void) { - return GW->get_overlay().is_editing(); -} - -/* TODO: removing menubar breaks accelerators. */ -void ToggleMenuBar(void) { - GW->get_menubar().set_visible(!GW->get_menubar().is_visible()); -} - -bool MenuBarIsVisible(void) { - return GW->get_menubar().is_visible(); -} - -/* Context menus */ - -class ContextMenuItem : public Gtk::MenuItem { -public: - static int choice; - - ContextMenuItem(const Glib::ustring &label, int id, bool mnemonic=false) : - Gtk::MenuItem(label, mnemonic), _id(id) { - } - -protected: - virtual void on_activate() { - Gtk::MenuItem::on_activate(); - - if(has_submenu()) - return; - - choice = _id; - } - - /* Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=695488. - This is used in addition to on_activate() to catch mouse events. - Without on_activate(), it would be impossible to select a menu item - via keyboard. - This selects the item twice in some cases, but we are idempotent. - */ - virtual bool on_button_press_event(GdkEventButton *event) { - if(event->button == 1 && event->type == GDK_BUTTON_PRESS) { - on_activate(); - return true; - } - - return Gtk::MenuItem::on_button_press_event(event); - } - -private: - int _id; -}; - -int ContextMenuItem::choice = 0; - -static Gtk::Menu *context_menu = NULL, *context_submenu = NULL; - -void AddContextMenuItem(const char *label, int id) { - Gtk::MenuItem *menu_item; - if(label) - menu_item = new ContextMenuItem(label, id); - else - menu_item = new Gtk::SeparatorMenuItem(); - - if(id == CONTEXT_SUBMENU) { - menu_item->set_submenu(*context_submenu); - context_submenu = NULL; - } - - if(context_submenu) { - context_submenu->append(*menu_item); - } else { - if(!context_menu) - context_menu = new Gtk::Menu; - - context_menu->append(*menu_item); - } -} - -void CreateContextSubmenu(void) { - if(context_submenu) oops(); - - context_submenu = new Gtk::Menu; -} - -int ShowContextMenu(void) { - if(!context_menu) - return -1; - - Glib::RefPtr loop = Glib::MainLoop::create(); - context_menu->signal_deactivate(). - connect(sigc::mem_fun(loop.operator->(), &Glib::MainLoop::quit)); - - ContextMenuItem::choice = -1; - - context_menu->show_all(); - context_menu->popup(3, GDK_CURRENT_TIME); - - loop->run(); - - delete context_menu; - context_menu = NULL; - - return ContextMenuItem::choice; -} - -/* Main menu */ - -template class MainMenuItem : public MenuItem { -public: - MainMenuItem(const GraphicsWindow::MenuEntry &entry) : - MenuItem(), _entry(entry), _synthetic(false) { - Glib::ustring label(_entry.label); - for(size_t i = 0; i < label.length(); i++) { - if(label[i] == '&') - label.replace(i, 1, "_"); - } - - guint accel_key = 0; - Gdk::ModifierType accel_mods = Gdk::ModifierType(); - switch(_entry.accel) { - case GraphicsWindow::DELETE_KEY: - accel_key = GDK_KEY_Delete; - break; - - case GraphicsWindow::ESCAPE_KEY: - accel_key = GDK_KEY_Escape; - break; - - case '\t': - accel_key = GDK_KEY_Tab; - break; - - default: - accel_key = _entry.accel & ~(GraphicsWindow::SHIFT_MASK | GraphicsWindow::CTRL_MASK); - if(accel_key > GraphicsWindow::FUNCTION_KEY_BASE && - accel_key <= GraphicsWindow::FUNCTION_KEY_BASE + 12) - accel_key = GDK_KEY_F1 + (accel_key - GraphicsWindow::FUNCTION_KEY_BASE - 1); - else - accel_key = gdk_unicode_to_keyval(accel_key); - - if(_entry.accel & GraphicsWindow::SHIFT_MASK) - accel_mods |= Gdk::SHIFT_MASK; - if(_entry.accel & GraphicsWindow::CTRL_MASK) - accel_mods |= Gdk::CONTROL_MASK; - } - - MenuItem::set_label(label); - MenuItem::set_use_underline(true); - if(!(accel_key & 0x01000000)) - MenuItem::set_accel_key(Gtk::AccelKey(accel_key, accel_mods)); - } - - void set_active(bool checked) { - if(MenuItem::get_active() == checked) - return; - - _synthetic = true; - MenuItem::set_active(checked); - } - -protected: - virtual void on_activate() { - MenuItem::on_activate(); - - if(_synthetic) - _synthetic = false; - else if(!MenuItem::has_submenu() && _entry.fn) - _entry.fn(_entry.id); - } - -private: - const GraphicsWindow::MenuEntry &_entry; - bool _synthetic; -}; - -static std::map main_menu_items; - -static void InitMainMenu(Gtk::MenuShell *menu_shell) { - Gtk::MenuItem *menu_item = NULL; - Gtk::MenuShell *levels[5] = {menu_shell, 0}; - - const GraphicsWindow::MenuEntry *entry = &GraphicsWindow::menu[0]; - int current_level = 0; - while(entry->level >= 0) { - if(entry->level > current_level) { - Gtk::Menu *menu = new Gtk::Menu; - menu_item->set_submenu(*menu); - - if((unsigned)entry->level >= sizeof(levels) / sizeof(levels[0])) - oops(); - - levels[entry->level] = menu; - } - - current_level = entry->level; - - if(entry->label) { - switch(entry->kind) { - case GraphicsWindow::MENU_ITEM_NORMAL: - menu_item = new MainMenuItem(*entry); - break; - - case GraphicsWindow::MENU_ITEM_CHECK: - menu_item = new MainMenuItem(*entry); - break; - - case GraphicsWindow::MENU_ITEM_RADIO: - MainMenuItem *radio_item = - new MainMenuItem(*entry); - radio_item->set_draw_as_radio(true); - menu_item = radio_item; - break; - } - } else { - menu_item = new Gtk::SeparatorMenuItem(); - } - - levels[entry->level]->append(*menu_item); - - main_menu_items[entry->id] = menu_item; - - ++entry; - } -} - -void EnableMenuById(int id, bool enabled) { - main_menu_items[id]->set_sensitive(enabled); -} - -void CheckMenuById(int id, bool checked) { - ((MainMenuItem*)main_menu_items[id])->set_active(checked); -} - -void RadioMenuById(int id, bool selected) { - SolveSpace::CheckMenuById(id, selected); -} - -class RecentMenuItem : public Gtk::MenuItem { -public: - RecentMenuItem(const Glib::ustring& label, int id) : - MenuItem(label), _id(id) { - } - -protected: - virtual void on_activate() { - if(_id >= RECENT_OPEN && _id < (RECENT_OPEN + MAX_RECENT)) - SolveSpaceUI::MenuFile(_id); - else if(_id >= RECENT_LINK && _id < (RECENT_LINK + MAX_RECENT)) - Group::MenuGroup(_id); - } - -private: - int _id; -}; - -static void RefreshRecentMenu(int id, int base) { - Gtk::MenuItem *recent = static_cast(main_menu_items[id]); - recent->unset_submenu(); - - Gtk::Menu *menu = new Gtk::Menu; - recent->set_submenu(*menu); - - if(std::string(RecentFile[0]).empty()) { - Gtk::MenuItem *placeholder = new Gtk::MenuItem("(no recent files)"); - placeholder->set_sensitive(false); - menu->append(*placeholder); - } else { - for(int i = 0; i < MAX_RECENT; i++) { - if(std::string(RecentFile[i]).empty()) - break; - - RecentMenuItem *item = new RecentMenuItem(RecentFile[i], base + i); - menu->append(*item); - } - } - - menu->show_all(); -} - -void RefreshRecentMenus(void) { - RefreshRecentMenu(GraphicsWindow::MNU_OPEN_RECENT, RECENT_OPEN); - RefreshRecentMenu(GraphicsWindow::MNU_GROUP_RECENT, RECENT_LINK); -} - -/* Save/load */ - -static std::string ConvertFilters(std::string active, const FileFilter ssFilters[], - Gtk::FileChooser *chooser) { - for(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) { -#ifdef HAVE_GTK3 - Glib::RefPtr filter = Gtk::FileFilter::create(); -#else - Gtk::FileFilter *filter = new Gtk::FileFilter; -#endif - filter->set_name(ssFilter->name); - - bool is_active = false; - std::string desc = ""; - for(const char *const *ssPattern = ssFilter->patterns; *ssPattern; ssPattern++) { - std::string pattern = "*." + std::string(*ssPattern); - filter->add_pattern(pattern); - filter->add_pattern(Glib::ustring(pattern).uppercase()); - if(active == "") - active = pattern.substr(2); - if("*." + active == pattern) - is_active = true; - if(desc == "") - desc = pattern; - else - desc += ", " + pattern; - } - filter->set_name(filter->get_name() + " (" + desc + ")"); - -#ifdef HAVE_GTK3 - chooser->add_filter(filter); - if(is_active) - chooser->set_filter(filter); -#else - chooser->add_filter(*filter); - if(is_active) - chooser->set_filter(*filter); -#endif - } - - return active; -} - -bool GetOpenFile(std::string *filename, const std::string &activeOrEmpty, - const FileFilter filters[]) { - Gtk::FileChooserDialog chooser(*GW, "SolveSpace - Open File"); - chooser.set_filename(*filename); - chooser.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - chooser.add_button("_Open", Gtk::RESPONSE_OK); - chooser.set_current_folder(CnfThawString("", "FileChooserPath")); - - ConvertFilters(activeOrEmpty, filters, &chooser); - - if(chooser.run() == Gtk::RESPONSE_OK) { - CnfFreezeString(chooser.get_current_folder(), "FileChooserPath"); - *filename = chooser.get_filename(); - return true; - } else { - return false; - } -} - -/* Glib::path_get_basename got /removed/ in 3.0?! Come on */ -static std::string Basename(std::string filename) { - int slash = filename.rfind('/'); - if(slash >= 0) - return filename.substr(slash + 1, filename.length()); - return ""; -} - -static void ChooserFilterChanged(Gtk::FileChooserDialog *chooser) -{ - /* Extract the pattern from the filter. GtkFileFilter doesn't provide - any way to list the patterns, so we extract it from the filter name. - Gross. */ - std::string filter_name = chooser->get_filter()->get_name(); - int lparen = filter_name.rfind('(') + 1; - int rdelim = filter_name.find(',', lparen); - if(rdelim < 0) - rdelim = filter_name.find(')', lparen); - if(lparen < 0 || rdelim < 0) - oops(); - - std::string extension = filter_name.substr(lparen, rdelim - lparen); - if(extension == "*") - return; - - if(extension.length() > 2 && extension.substr(0, 2) == "*.") - extension = extension.substr(2, extension.length() - 2); - - std::string basename = Basename(chooser->get_filename()); - int dot = basename.rfind('.'); - if(dot >= 0) { - basename.replace(dot + 1, basename.length() - dot - 1, extension); - chooser->set_current_name(basename); - } else { - chooser->set_current_name(basename + "." + extension); - } -} - -bool GetSaveFile(std::string *filename, const std::string &activeOrEmpty, - const FileFilter filters[]) { - Gtk::FileChooserDialog chooser(*GW, "SolveSpace - Save File", - Gtk::FILE_CHOOSER_ACTION_SAVE); - chooser.set_do_overwrite_confirmation(true); - chooser.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - chooser.add_button("_Save", Gtk::RESPONSE_OK); - - std::string active = ConvertFilters(activeOrEmpty, filters, &chooser); - - chooser.set_current_folder(CnfThawString("", "FileChooserPath")); - chooser.set_current_name(std::string("untitled.") + active); - - /* Gtk's dialog doesn't change the extension when you change the filter, - and makes it extremely hard to do so. Gtk is garbage. */ - chooser.property_filter().signal_changed(). - connect(sigc::bind(sigc::ptr_fun(&ChooserFilterChanged), &chooser)); - - if(chooser.run() == Gtk::RESPONSE_OK) { - CnfFreezeString(chooser.get_current_folder(), "FileChooserPath"); - *filename = chooser.get_filename(); - return true; - } else { - return false; - } -} - -DialogChoice SaveFileYesNoCancel(void) { - Glib::ustring message = - "The file has changed since it was last saved.\n" - "Do you want to save the changes?"; - Gtk::MessageDialog dialog(*GW, message, /*use_markup*/ true, Gtk::MESSAGE_QUESTION, - Gtk::BUTTONS_NONE, /*is_modal*/ true); - dialog.set_title("SolveSpace - Modified File"); - dialog.add_button("_Save", Gtk::RESPONSE_YES); - dialog.add_button("Do_n't Save", Gtk::RESPONSE_NO); - dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - - switch(dialog.run()) { - case Gtk::RESPONSE_YES: - return DIALOG_YES; - - case Gtk::RESPONSE_NO: - return DIALOG_NO; - - case Gtk::RESPONSE_CANCEL: - default: - return DIALOG_CANCEL; - } -} - -DialogChoice LoadAutosaveYesNo(void) { - Glib::ustring message = - "An autosave file is availible for this project.\n" - "Do you want to load the autosave file instead?"; - Gtk::MessageDialog dialog(*GW, message, /*use_markup*/ true, Gtk::MESSAGE_QUESTION, - Gtk::BUTTONS_NONE, /*is_modal*/ true); - dialog.set_title("SolveSpace - Autosave Available"); - dialog.add_button("_Load autosave", Gtk::RESPONSE_YES); - dialog.add_button("Do_n't Load", Gtk::RESPONSE_NO); - - switch(dialog.run()) { - case Gtk::RESPONSE_YES: - return DIALOG_YES; - - case Gtk::RESPONSE_NO: - default: - return DIALOG_NO; - } -} - -DialogChoice LocateImportedFileYesNoCancel(const std::string &filename, - bool canCancel) { - Glib::ustring message = - "The linked file " + filename + " is not present.\n" - "Do you want to locate it manually?\n" - "If you select \"No\", any geometry that depends on " - "the missing file will be removed."; - Gtk::MessageDialog dialog(*GW, message, /*use_markup*/ true, Gtk::MESSAGE_QUESTION, - Gtk::BUTTONS_NONE, /*is_modal*/ true); - dialog.set_title("SolveSpace - Missing File"); - dialog.add_button("_Yes", Gtk::RESPONSE_YES); - dialog.add_button("_No", Gtk::RESPONSE_NO); - if(canCancel) - dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - - switch(dialog.run()) { - case Gtk::RESPONSE_YES: - return DIALOG_YES; - - case Gtk::RESPONSE_NO: - return DIALOG_NO; - - case Gtk::RESPONSE_CANCEL: - default: - return DIALOG_CANCEL; - } -} - -/* Text window */ - -class TextWidget : public GlWidget { -public: -#ifdef HAVE_GTK3 - TextWidget(Glib::RefPtr adjustment) : _adjustment(adjustment) { -#else - TextWidget(Gtk::Adjustment* adjustment) : _adjustment(adjustment) { -#endif - set_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | - Gdk::LEAVE_NOTIFY_MASK); - } - - void set_cursor_hand(bool is_hand) { - Glib::RefPtr gdkwin = get_window(); - if(gdkwin) { // returns NULL if not realized - Gdk::CursorType type = is_hand ? Gdk::HAND1 : Gdk::ARROW; -#ifdef HAVE_GTK3 - gdkwin->set_cursor(Gdk::Cursor::create(type)); -#else - gdkwin->set_cursor(Gdk::Cursor(type)); -#endif - } - } - -protected: - virtual void on_gl_draw() { - SS.TW.Paint(); - } - - virtual bool on_motion_notify_event(GdkEventMotion *event) { - SS.TW.MouseEvent(/*leftClick*/ false, - /*leftDown*/ event->state & GDK_BUTTON1_MASK, - event->x, event->y); - - return true; - } - - virtual bool on_button_press_event(GdkEventButton *event) { - SS.TW.MouseEvent(/*leftClick*/ event->type == GDK_BUTTON_PRESS, - /*leftDown*/ event->state & GDK_BUTTON1_MASK, - event->x, event->y); - - return true; - } - - virtual bool on_scroll_event(GdkEventScroll *event) { - _adjustment->set_value(_adjustment->get_value() + - DeltaYOfScrollEvent(event) * _adjustment->get_page_increment()); - - return true; - } - - virtual bool on_leave_notify_event (GdkEventCrossing *) { - SS.TW.MouseLeave(); - - return true; - } - -private: -#ifdef HAVE_GTK3 - Glib::RefPtr _adjustment; -#else - Gtk::Adjustment *_adjustment; -#endif -}; - -class TextWindowGtk : public Gtk::Window { -public: - TextWindowGtk() : _scrollbar(), _widget(_scrollbar.get_adjustment()), - _overlay(_widget), _box() { - set_type_hint(Gdk::WINDOW_TYPE_HINT_UTILITY); - set_skip_taskbar_hint(true); - set_skip_pager_hint(true); - set_title("SolveSpace - Property Browser"); - set_default_size(420, 300); - - _box.pack_start(_overlay, true, true); - _box.pack_start(_scrollbar, false, true); - add(_box); - - _scrollbar.get_adjustment()->signal_value_changed(). - connect(sigc::mem_fun(this, &TextWindowGtk::on_scrollbar_value_changed)); - - _overlay.signal_editing_done(). - connect(sigc::mem_fun(this, &TextWindowGtk::on_editing_done)); - - _overlay.get_entry().signal_motion_notify_event(). - connect(sigc::mem_fun(this, &TextWindowGtk::on_editor_motion_notify_event)); - _overlay.get_entry().signal_button_press_event(). - connect(sigc::mem_fun(this, &TextWindowGtk::on_editor_button_press_event)); - } - - Gtk::VScrollbar &get_scrollbar() { - return _scrollbar; - } - - TextWidget &get_widget() { - return _widget; - } - - EditorOverlay &get_overlay() { - return _overlay; - } - -protected: - virtual void on_show() { - Gtk::Window::on_show(); - - CnfThawWindowPos(this, "TextWindow"); - } - - virtual void on_hide() { - CnfFreezeWindowPos(this, "TextWindow"); - - Gtk::Window::on_hide(); - } - - virtual bool on_delete_event(GdkEventAny *) { - /* trigger the action and ignore the request */ - GraphicsWindow::MenuView(GraphicsWindow::MNU_SHOW_TEXT_WND); - - return false; - } - - virtual void on_scrollbar_value_changed() { - SS.TW.ScrollbarEvent(_scrollbar.get_adjustment()->get_value()); - } - - virtual void on_editing_done(Glib::ustring value) { - SS.TW.EditControlDone(value.c_str()); - } - - virtual bool on_editor_motion_notify_event(GdkEventMotion *event) { - return _widget.event((GdkEvent*) event); - } - - virtual bool on_editor_button_press_event(GdkEventButton *event) { - return _widget.event((GdkEvent*) event); - } - - virtual bool on_key_press_event(GdkEventKey *event) { - if(GW->emulate_key_press(event)) { - return true; - } - - return Gtk::Window::on_key_press_event(event); - } - -private: - Gtk::VScrollbar _scrollbar; - TextWidget _widget; - EditorOverlay _overlay; - Gtk::HBox _box; -}; - -std::unique_ptr TW; - -void ShowTextWindow(bool visible) { - if(visible) - TW->show(); - else - TW->hide(); -} - -void GetTextWindowSize(int *w, int *h) { - Gdk::Rectangle allocation = TW->get_widget().get_allocation(); - *w = allocation.get_width(); - *h = allocation.get_height(); -} - -void InvalidateText(void) { - TW->get_widget().queue_draw(); -} - -void MoveTextScrollbarTo(int pos, int maxPos, int page) { - TW->get_scrollbar().get_adjustment()->configure(pos, 0, maxPos, 1, 10, page); -} - -void SetMousePointerToHand(bool is_hand) { - TW->get_widget().set_cursor_hand(is_hand); -} - -void ShowTextEditControl(int x, int y, const std::string &val) { - TW->get_overlay().start_editing(x, y, TextWindow::CHAR_HEIGHT, /*is_monospace=*/true, 30, val); -} - -void HideTextEditControl(void) { - TW->get_overlay().stop_editing(); -} - -bool TextEditControlIsVisible(void) { - return TW->get_overlay().is_editing(); -} - -/* Miscellanea */ - - -void DoMessageBox(const char *message, int rows, int cols, bool error) { - Gtk::MessageDialog dialog(*GW, message, /*use_markup*/ true, - error ? Gtk::MESSAGE_ERROR : Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK, - /*is_modal*/ true); - dialog.set_title(error ? "SolveSpace - Error" : "SolveSpace - Message"); - dialog.run(); -} - -void OpenWebsite(const char *url) { - gtk_show_uri(Gdk::Screen::get_default()->gobj(), url, GDK_CURRENT_TIME, NULL); -} - -/* fontconfig is already initialized by GTK */ -std::vector GetFontFiles() { - std::vector fonts; - - FcPattern *pat = FcPatternCreate(); - FcObjectSet *os = FcObjectSetBuild(FC_FILE, (char *)0); - FcFontSet *fs = FcFontList(0, pat, os); - - for(int i = 0; i < fs->nfont; i++) { - FcChar8 *filenameFC = FcPatternFormat(fs->fonts[i], (const FcChar8*) "%{file}"); - std::string filename = (char*) filenameFC; - fonts.push_back(filename); - FcStrFree(filenameFC); - } - - FcFontSetDestroy(fs); - FcObjectSetDestroy(os); - FcPatternDestroy(pat); - - return fonts; -} - -/* Space Navigator support */ - -#ifdef HAVE_SPACEWARE -static GdkFilterReturn GdkSpnavFilter(GdkXEvent *gxevent, GdkEvent *, gpointer) { - XEvent *xevent = (XEvent*) gxevent; - - spnav_event sev; - if(!spnav_x11_event(xevent, &sev)) - return GDK_FILTER_CONTINUE; - - switch(sev.type) { - case SPNAV_EVENT_MOTION: - SS.GW.SpaceNavigatorMoved( - (double)sev.motion.x, - (double)sev.motion.y, - (double)sev.motion.z * -1.0, - (double)sev.motion.rx * 0.001, - (double)sev.motion.ry * 0.001, - (double)sev.motion.rz * -0.001, - xevent->xmotion.state & ShiftMask); - break; - - case SPNAV_EVENT_BUTTON: - if(!sev.button.press && sev.button.bnum == 0) { - SS.GW.SpaceNavigatorButtonUp(); - } - break; - } - - return GDK_FILTER_REMOVE; -} -#endif - -/* Application lifecycle */ - -void ExitNow(void) { - GW->hide(); - TW->hide(); -} -}; - -int main(int argc, char** argv) { - /* It would in principle be possible to judiciously use - Glib::filename_{from,to}_utf8, but it's not really worth - the effort. - The setlocale() call is necessary for Glib::get_charset() - to detect the system character set; otherwise it thinks - it is always ANSI_X3.4-1968. - We set it back to C after all. */ - setlocale(LC_ALL, ""); - if(!Glib::get_charset()) { - std::cerr << "Sorry, only UTF-8 locales are supported." << std::endl; - return 1; - } - setlocale(LC_ALL, "C"); - - /* If we don't do this, gtk_init will set the C standard library - locale, and printf will format floats using ",". We will then - fail to parse these. Also, many text window lines will become - ambiguous. */ - gtk_disable_setlocale(); - - Gtk::Main main(argc, argv); - -#ifdef HAVE_SPACEWARE - gdk_window_add_filter(NULL, GdkSpnavFilter, NULL); -#endif - - CnfLoad(); - - TW.reset(new TextWindowGtk); - GW.reset(new GraphicsWindowGtk); - InitMainMenu(&GW->get_menubar()); - GW->get_menubar().accelerate(*TW); - TW->set_transient_for(*GW); - - TW->show_all(); - GW->show_all(); - -#ifdef HAVE_SPACEWARE -#ifdef HAVE_GTK3 - // We don't care if it can't be opened; just continue without. - spnav_x11_open(gdk_x11_get_default_xdisplay(), - gdk_x11_window_get_xid(GW->get_window()->gobj())); -#else - spnav_x11_open(gdk_x11_get_default_xdisplay(), - GDK_WINDOW_XWINDOW(GW->get_window()->gobj())); -#endif -#endif - - SS.Init(); - - if(argc >= 2) { - if(argc > 2) { - std::cerr << "Only the first file passed on command line will be opened." - << std::endl; - } - - /* Make sure the argument is valid UTF-8. */ - SS.OpenFile(Glib::ustring(argv[1])); - } - - main.run(*GW); - - TW.reset(); - GW.reset(); - - SK.Clear(); - SS.Clear(); - - return 0; -} diff --git a/src/icons/angle.png b/src/icons/angle.png deleted file mode 100644 index 7001a9ddc296773cafea0ef5e2902b0a0a1f9a72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27359 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 NcE##)iP@c}{{dA>m_q;n diff --git a/src/icons/assemble.png b/src/icons/assemble.png deleted file mode 100644 index 5db2d6b08d74a7eba9571aab2cb5a81ca823a260..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27011 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 diff --git a/src/icons/bezier.png b/src/icons/bezier.png deleted file mode 100644 index 73620cdd758e13b96e9e2bbd18e3501b92614154..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27089 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

BB1vyr>=hu`W6H;Qj6k3ah3<#C*ivdm-4$Cu7O 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 z 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 diff --git a/src/icons/constraint.png b/src/icons/constraint.png deleted file mode 100644 index a0e1889ae80a08b643a0749fd5ec55fc085b68dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26545 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 diff --git a/src/icons/edges.png b/src/icons/edges.png deleted file mode 100644 index d89c046fe6e97889a10ff9fdc7205d9f9c38b40c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27374 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 diff --git a/src/icons/extrude.png b/src/icons/extrude.png deleted file mode 100644 index 6d4a44a138f74c755490e5e349bf0216dccb8f0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27030 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 diff --git a/src/icons/faces.png b/src/icons/faces.png deleted file mode 100644 index 8fd4e363a97f892618b7fdcfe229f6aa0b38a61a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 609 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;YR|DNig)WpGT%PfAtr%uP&B4N6T+sVqF1Y6Da>C%`AfRasfNu~AD{SlG?Yt+KK* zH#b*WT6+3)>Ez_(sHi9d1B1Lgv(1~iTwGk7ot^FM>>M2(wY0RNqodQ))8pggi;9Zu z?d|#b`JJ4cJUu;KU0uDsy(1!AGcz;!`1mX=EOd2sEiEl;YjtB|9hWQ-mXnk7^788G zQTFig;Ns$PaBxUVvvGHK4-XHwv9U2XH=jM5@z5cL`ucjH1HHO03o$S-dV9J!hE&{2 zGH7FBWm8K|VGwZO3Gni7?vHd2Vpn7oa7f@Zvz4u_t*w)dRcv8#U@&ZGW&QH?Ye`9K zZ;7A~1LKheoMK@q*Pa>}IZb2WP+((Am6c6PixpH>4&iKINV;HbVh|FTZs;@}Bx%dM zpl6$48_<}>Qi+6#K%qHS3``u$`5G7wtz=ecm{7W*!{hafmxYc@yg*(_!HZ9?nw{7r zGORuoF)*@RKe+Jp8h++IyPO^Yc~=$JtY#K}cKKmSVk$F-LI=7Ft5=!bFVdQ&MBb@0M#DENB{r; diff --git a/src/icons/hidden-lines.png b/src/icons/hidden-lines.png deleted file mode 100644 index 35ea04b21340a6976600fa67b3bab7ce86f1eaa0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmV+*0^j|KP)(_`g8%^e{{R4h=l}px2mk>USO5SzmjD14Z`WEM zkN^Myrb$FWRCwCdRxzr=Kop$)A&{t`D1u-k0R?T68+ZfnVd)XXdni~~3RYggcDJ<< zPhhO<)a?A?qa?Z;{d_F`gK1vgo7vsG9aaD!J{Vv4aJ&?l&1TSA{|aRGP-sG>QsHvB zBuREU9op({a9x+<@tB9hf&5Xp!A!1{;%2jXEVg1YnJ|uHhGCd5&}y|PrQ~Y0A^_Iw z^<3gcqrt^u!QF1hFboO6E)Yc#kH;hD^Eqp^8UauUVI?mV3S6((lv1)>F5mM;fhdX? z$1z8vQHJDlxfscVAfS|zN~sL_qky=pkRS=Ib!Gw_$3c=L#tCyA2SSLaJjqPnw5>ae z#iI3yY(3T;m{yY)ww`qU>m8E+Z56KTepbNqJnOgf`TTc*)9D1QHJZ&PeBZ}*yM@*o z)oK+5zqu1+Y%+wJztaZH=tU@&;T$?0O>_o$8$ z>>lc}#h&L8086FPO9v%sG&`%n&FqH5VXn!gdrW3$6)>5d4M7l)q|xlG0$OWcuh+LR iolajaMEv_##rFVq{XdKSQl7{F0000oU&Gpo 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 WLQ`h$%F^GmyLjI2T=AJ3kN*#1E;ls* diff --git a/src/icons/in3d.png b/src/icons/in3d.png deleted file mode 100644 index 0d6be0760e134b6f34e2af3ac13cb9654a03b916..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26608 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 PyIsC$bD`wittbBn>2u8s diff --git a/src/icons/lathe.png b/src/icons/lathe.png deleted file mode 100644 index 3596fe3ea9d7f6335f5bf29485898f3c1e6deeb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaP3?%1DUd;wlJOMr-u0UE&PA)Yy_0OL_yS_}+ z0P>?eT^vI=uJ@i>D9B_e;Be8rlQaF&p;sX@gZzxVg=c0rm`ym7;HSiR-1vT7-PALk z+RaSCmpP;BH)f}sI$0Z~XRQ7d-xq!Eg4|V(+pFH_trkA@_vDk58nu0U8KpN#nI<{B RU>G)JG1}* diff --git a/src/icons/length.png b/src/icons/length.png deleted file mode 100644 index 8396bb147fdd75595f19381a9e143c0235aa483e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26577 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 E0s8?7x&QzG diff --git a/src/icons/pointonx.png b/src/icons/pointonx.png deleted file mode 100644 index ad15c56e8c8750b87ebf5f20a4f6e2c1001cce7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26848 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%0AHQLBV5naJDbdYY@W9ZR6aYWH<0U1@g`+yudhJ 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 Ty?Nzt;axm$cdq!%&By--A5A^C diff --git a/src/icons/ref.png b/src/icons/ref.png deleted file mode 100644 index afd64c3fa0c39c7115fdfe9257775e185161a4f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25761 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 Za}DLd8BCI#@E=*8JL7zsaPr!p{|D3G4sZYf diff --git a/src/icons/same-orientation.png b/src/icons/same-orientation.png deleted file mode 100644 index c612ee5f4a664e3070b1338b7fada83f5b68d76e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26407 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 diff --git a/src/icons/sketch-in-3d.png b/src/icons/sketch-in-3d.png deleted file mode 100644 index 204fd8c96718ef6d66513deb07b2f117f2fedcdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26669 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 B4rl-X diff --git a/src/icons/sketch-in-plane.png b/src/icons/sketch-in-plane.png deleted file mode 100644 index 86811e8bc993d1f5fec0d220ef8428613ea2c0d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26671 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 Al>h($ diff --git a/src/icons/step-rotate.png b/src/icons/step-rotate.png deleted file mode 100644 index 6c38a16765bee3e88ad5bbaf18af0531a4b7df7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27352 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 KrIHJ`pZ*`svotsW diff --git a/src/icons/step-translate.png b/src/icons/step-translate.png deleted file mode 100644 index faaeff0b977198b20138a6aa1e4eece11d7bc01d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26253 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 F{|DDI3FZI* diff --git a/src/icons/symmetric.png b/src/icons/symmetric.png deleted file mode 100644 index 6eb9cad9b31cfc18f8a2a9987e7c77cfda2d6e46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26180 zcmbq)V{|4>5N>SS*2cDNZL%>owr$(l*xGPo+qP}ndXv02-^Gu6|K2`R(=$_bs;j%t zsjBX$CsJ8a3K0$u4g>@QQAS!^^}ELTk6@s_@4M~-uiq7fv#5+3%y;pDF^TwohjozF zas~l`NBfUJ=ePVmzCU8QNNBo-QrrqSy!=-Y!wEp;+iYoDYf;Jf?eVYgXNYh5 z|0+L<*$O~FaGh+e)qZ{tg`SrC#&FWG$^L4RDc^KXvS&JW4x9NjzXUdZixgvFS$Yyq zP*NH;v0W{XBSky~-O4~PP*8no%Mje6s*vIs?^hvIN1cnW-jlkks~Er6QoEL0_|?$- zZCDQ>Q|j?zrqcOB=F8t5x3u<`RLmM=zU%qMuI;^u@xAT?M(e*^E{fQ*o4f&ht<{Ra zPLUq}1{=g(jSil6Z7Ze5NY15gYX;jYJxh4ob3!6wkx!(>JTYJ7bEc+@=E|pnC*3UX zf$1>_`wjlT9b7p>^O-AMzfK(L8oQR~=8Sm4!74`dC)P@nvl-#$oV*^(m&}LgXxB$R zpa6RB`~-7tTUfs5NafsSDbpa+IbQ!ztqJNn;t7^L2Cqk=2B@CrCrHktBg(|CL;+x~ zY_`=?c67CMj$qMsuEO#f9opzG7ZGbvH1+=E(VtAxV73B2sg|!{@_PKyz zQdeojIg4U?l6a1j;`Xfq!5jDB8%r$%O)C~jaIuT}5?QDh2u9^E5O6DIqGH!Z!={|$ z6z~`K&_C!Kkx4!^0ad<>-A+Eu6e(}uaZHWT;x-6&WML^8pom+)HX?Ej$uV<4#fHOs zTMxw9NKLBR#}evdwCpI7VoN!Vg%?W^!rw9p@^TNMCxiGCr8akZDX=_#3F~(B4{}fy zIJ&p>@+Ny^nxJ!=oE<2tk;%mJho~+DltvK}y6;d#W%i?JQ=7n@MA90vFIR$JJYi~4 zdjY%N-)wt}qP} zd1FmAe=v`KtoFk{;@N89W58Ae7sNO&fTLl7P*nPJgctd_+0c*Il~!vk^EXFN38 z+WZ!(`WEW+p0$7987w?>(+rOmCXD=pG6~E%dmqHnDEBA9W2<1!ozp0&3zToQMJK_t zGwtSqp?oH^n$hYa%^x*Zt_tUC<2$2}CKZJ+R~U=T2Ocph`MNO}jL3n-V&h+E2esuH zJLexXRYt5NIAk(#RCMuYPyws=fx_LNvVO(VK12vJKdR}u*Cvg$&2j)bQ6>v9(WnSS z@|y{ciGhzWn5=&$Px+reZ!`e{rgqxqtL!+O7^2dvr!7OnW~Hk87LRk zoA!a4Ohrhe^!0F$vq3bol{3!y58T33Cl^y5X7ZsBeHMqDFa|E-5?G(mjPJ11)@{8T z-6vfk+A+35B_8RHbK*>_hMBX!Mtfl1F&r6w=nQt?#L!3LWle_W|5B@rp%uydlV}AV zVJyApNh{Apna=Dru-OX!b6Cm2=O0O|$?V^!$+F*S83|Sl- z@!GeBG)Q27hcZ9FQBM%VHsuh*JIzkTjSVsXDt)d3i5wcuG8_dMw zsPn-_CIusTCMP})-g`(8R~Tht(Ib2TRx3AU4B?y=dZHy0?PUdBT8YSzl@F{&!&oeW zZCbf~!{+V}K&H+rcfNWOO&A!VBV}e4B_a5Qzqv&HYM>e6^OttK#DbHv9k+%n96yOK z)R=IZNF{2h3|fakABj7UyJ|8l-9ZZ4kJBR%>CEoeTj?5juP)v1Y;l_TM67;MhbPFX zTX5WbbAtSp!^9JQ0|{D;Oota537_O7GNgaF?+qn3(Ie^Z;xICz8Uiwp4V?(Cs2%#{YJ(EP_!~QBzhGF zf+J#_K~&YxBj(K;Yz&TL;k&5f<7NgyGk&IQ{B#o!v2)44=B0#bR$E5XPE}8C$Q2kQ z=ZywqsY6f7Pfs*NB_cwAK#NPqLV$d_EvHkBR+)%M&}_RjQmg7WH-a`(6;-*HFG0(} zCM}f3tUQ4sAf=Ph$jmlU`cf&+U6s~t{w~f}dH+{= zDH^$D1#ex3>_~a!+|WKZVS!9O%#Td%exu-r$S-5wz;|m|mEtQ2{cslb`qFyQeD?kN zJIENBQ|RkDE#<=F=_m5~TI(N}#VW{DYkHKG6=_tDry)Km`X`K9iq|aaLAlGclfc8p zeDkl%vmm3x_yCffhCfwOkab3)HKCEsFP2(NzY1V^ zP{4DI{D3C-6yA;q6O-Lt34ZgAq3^Qmac;P*zTFD*{94R=D5@Uc?n}7`Exckch&|Xa zMRUtYbjvW@@>T7=t`++Zp;d@EX)6M^e2-{$98U{{Y=RLhszz_)rMW*0$R);et$KnJ zBPpz2b`b{o?G}QOk@j8<1MF>F!D~!p6v8ZwfcziSeUI4#>>=Cod$8OJZy*(6O5KlB z+eaIJ)lRa_xJz+-4yM9y%4C(JGpy3OHuxc@e64VM*a@$?7Kuc154e6srdv+Is+eU8Ap=!iT9#xO~u+|321gyUd zj;Ji=4KHF}4Ea378cn>&!^3n5El7qi|Et#SZ)~^v0X1v$0O~afYNY~oH$Fqx{)Kca zXJFsoG$fp1CrC5HO#}EyDV}Lm5<&XPb)-=<&;FaMg3vmA3{IV1CfAiNk#3>xkN496 zA@kh;Iumq=?B|)O#U7UUr=`3vi-naxp#{Nx4E&iMSXvEedbSA{Ik@wE+W&enSBvt?kklW(J&voPJEgZlbtW;+LBW;B2OaIp#-l_R9*1unsjx>u;?L^DFm~cz9#$uFk9>1qz z#R3Jm!z!(AcmAmuZsIjLQ65n%m*#H_jOF)QZEFK+5(p#yIb&6%(maA~{(0C(H4cW>d(fj$exgK~h@uH?wo!aeR)oG(Y4x z{EP9`Tn6RaSd5gCq;RNPqi;3Ar^c5tF5a}9t-&^M+ z*d8_#aw2X%%Iq-DO7OkYo59Qmc#p2!Ngp?FWPk4QxLzgvN}oMw+TT0))YoJ7oj8%twH6_@jOb%+ODGa<4KSm4=D$bLC+(@m$yK)FXS135}!`VXbhDzA-WKxk_93xNr;VDL3vs_#^b{rN@94W(aDyqxaG$mi!W%lrNN>a`Z z#|R6A=dGuO>wg;b{!=xEpvQpwu8;FhJw1!GsnZENHeD*=c79ZKc$w57H_gby7uqnD z)NsR5k;>g+a+*{i7siOo8(I*XRIoBS3pX~Am?V zTjLjRw%&Co9d7%7&3_9bPs|c>!vh#%>wqtyxMDS5llOpLt>4XxCy^|x-eL`V=1{6D z#M#tI9DG$A2huhq281IZ9FT00)~m#DdY0ii4tDF;7H^wnO7$RJ<6Wn@v!z=B)rU(0 zb!7blJ6xsKOVZaQdMmRdSv-yZzG5vloU3i?nGmFklv=xgPlJ#JJLNfEYqD2)-ql&W zZWc=~!ky{iM*7JmmL*1uP3R40VMSh7y<*^K&j;u*v-=_M2HE^IIaM&UXfQdoPcPsS z(Du%h281*lWZEwTaOw>!P}-evMUkIl6gqd6g|^k>RzX zw=P`Wt7BdV>fQ^S7FgXH54<54k$OI;?-3fAcFPhLR5VhSdGpPV;EF>Vz%=N=df$bz zD%4lPTU-F#T-Ak7l+A4{+wt>iZfYUm&HS+#)hK&!DiS7p{ZmUFAx3&7 zGh>V*?WL{xj=WA$`dCRy7v+V2A^J0Af-0b0| zxd;wtt-e_I7@UTG&G`3|?`P|M5E^-TvJc3k=;!^6HRrwI{~1#jbifKYL@rxen02dED^>7vF^#-IKiz|v$nC^dAB%m(v2&+Y+sGNnH&4vHs*UE6Pje1o{>;>g`H?mU zGnBw>cOcvye0C`CkW5v8Gl}h1D+p~UR>LwfI-^7rW5?5;v~55)a>o=&ai#4_XM21y7@2KDlFvA z1mOKac+<9fmgiB^4!X~l_Dx3G#j)Uu7XFAUEyFs5q<8s~v^}u8?gj%WdcW(O5PZ-m zu+uvE;mBgo!iZBK<()ocZWrwTyE((}8YvqP>2tSe9!D~Net8rBr+ngO=0`NZE`G z8a8xOv%*PvX;kGu_T5`mgXF?!JJMMIpAYi9m#CHnH&3imSoilBo>Q>P82!1R9UPU| z$edE;^~MJkvlF;~iY7W5J@{ABXGEN zup9W;PgG6NO)a%m$l>pbwa>qN!ufdQ-+THWkPyemR>)GRo&)$XzV1|oHl;fQ{Bjqn zwU_HJZuV)%eMEr9_O(EnWWDRYnLj+bIe=U<7LCX0E!W|$F@L#MDR#c^a0AGwF@Of_ zq#S>W&s}J=La`axXsl{*H^0$CYmB_K36Aq2&&kzpRS$JJn^~NaIwsUY;iGsHOwd zf}5|qR=uCM9`%Yx8^&&ieTW~3f-<9r4Cqf2)pc#yk;7l)ZNQMj z*9uJbI*j)AZ_t+Huo$&oNwrq_oC3nSM4Vk3Y?=VkCGbF(E!Qr)-JjOG4NvH&sP(SV ztL6m?pQ~E8vy6ARwTbR{8?2Yv@~Q1BRzysx==iecb&bcaHA_6Y^oTXCE{&Sg1p()E zD^AY^^Pvmbj7_{ znsPapb)9*Ac-4cuN#7wwereI6gogA^v6nA%ur6fgD%QSXI4G2xFFhrahlzwa#YxAj z#!WuxKYOza3GM6EUlY88H36531n}sO0*yNqc|nObAmlekI1&VK_iucjH{Ski5z61- z@%>nISFAg&Exd85<@S^7I^$!<=t7MZv1e~b^UpR@9RHwBY23Z)I#YGuCQvbu8YqJ8 zK#S%g#OpdB2bjV3U`D31sNU_CAb$cX>Q;6U*x79Ot9!@0F4H}AN+u)W}eIhyG)|l zv`|GvA70AvOLE{0X8Hg20;(Lv2i62|Ns9-Jxvg#FuhwjMpCzg7H%oqty@Twvz)Z3pCcX=cvSHz(jf z=f+~sb2<%dR@QD$9UIpomL4$Dc`wN}=?510Lbup-Suen_?|^oA-20x-e_ysPN1+au z_}&q+6W%oKoDnzchNr!W?#=P_m$)S6Cx05vmQIq53t8Sc_i{_NC0=fVKI#A6u zi%~Y`v|zqi$hgCuynMX;wGx4+ckR(okA3IX0>_EZL(fPxi{N&~gl(Go*Y)K0ad7&t zYs2}KgLm!yQT|j+bTKHe$253ixq`5V#sMql(4Cy;`eQ7)%;ZqearJyPziRx3bbb`fC}TCL^>ZJu{* zMbP&3&CJcr*(xFs{+8g(6+wkaDqbPD<61u=gC5l{pN0yi$dt_ecE!IE`Lp)t3sD3f zYHtxQChF4iN(UBcw`zNo6G3xC(8iFd*_5cKmAVul8l&*WCaJ2rd41 z57geL$G{uY3kp23q7GS>lg6X%ks`v`BHI zL}?1F$QXRJJT^YQh6au0sHlh2Yp)MIVg7C=qCZt5ZnYI4t4{Gc7f(bEeKLEHtL}R- z$N@88TZUTrkeUJD76DlUh&dJfw^}WuZrx#OKB{(@27bf%0V@tXhCv;8qY69TdnMr6 z7odcS1=F=YTFt&`6K=v#C^}3x`FxeiZ=Avgm>MV70TsEXt9?9~pcT&$*Pn|782ji$?ui=8IVtF53q5VvA+ zQtr@j9PzNydjU@tr-WZwtu7;mFT!h~l^uD1!>M z@pO5I1{>ZMcz16F8@GUaI|261^ttL5Ty9@M(auv^THET~C!pw&+^r6X#MhNxHg^pC zrAiLt!MttL+?E8Xdfpkd<^+j!K|B}N!aP@9!1j#cr}|v?YnUj?bFp)akLCixj}6Y? zwj#pt+}!rvLPNr*LNC?3%VY^sgs-al*mLl;0q^g>CDOt;aiu+e@k$O3aXzXD$}Rl# zd|`a1!rt({3!dQj6)`#Fg^|Nl{74aEy+y|k1|rz8W!LULUvkl-DG?}0D3YV#OG_FG z{l#K{%dslN=F#i>vOtKBKA`wnUK1E6yb0Le zpp2pP;V~Qzo%Q7`TzP|L6w17}!c@<=z(9e`2Xe`Yih(}?$9m?^Q7_@zTzE6Y8$mf#^l>~q3TYQMKo70yAHOk5B(Rnlx*~*?T z@r)ztH83x2;>>DAc@F$uq(n-+Q`hY!?Q2KGVNc^=HkVA~w z?f2D|_0qc6DKnawbs58b_O%O}Nl27}&^p+4P(y_zHsH%j#9jn151$VvXoRi_J+-bW z>2KX+u_?KwA=3g{SH#%63je`i!H^p4d8yu04Pv+r)l_gIiFj?x?`xrodj#pGHg6VR ziR7E>WAb=7L{O+EtkkWnyC(BaO%n3i#~g%nvq-7V5MuPPe!ciQ#9F(YoLJ@QkvH=W zxpa4UYLdCUqu(=Jw7g&(C^DD~;4g{m&Q|cAuX`w$mi>8)I!V^)GX?-Q=rMi$^>V ztaR^I9z*@1IW|>qUh|kI78-3ak5?2$V`&Uwz)N#=#c9|*E8$E28#L}cAs{qv> zOb04R4}S?9V7*g%tKR1knI(UgUDKYCW9VtM5*lNfR^MrG;y*hB{@dG3`f--2XI;Zz zU@!vY!;6gaL_h{Xmm&f^hW(#@)Y#l+#kQnqXMdKAFTr68vcjg;9AhsFeg z&#SvCK?|ynuh=&!9De=<6omD4B)Abek zGd6?Nlf|K)BqPdV6uTZzMCe3zhPh5}bvkMWCnz3@cBGX_Yx;pO3X$qyw3WOJ>y%YW zZ^Y4?m^nyIbAqkORI7OKK$0*s8y8n<-HTAf*ZVWD_-^|)KiMF;n`sn0_$e$J!#LrA z1)HC`4G5A}E7S679PFMP13DCLErKn%SsZ1L2cA(+|EDh>=L)jD7oTuUk?Nqp`D7Pr z;MfiAP`(K7?&>MpA;Ioy*1ZOQ)!iPSRqdnrwB>eyx<$C!@{HM+B*C@cJ;oXkM(T@} z9SFEhKe6qO%HH!V`U>cGe7-gvb3kdhL5I-It%ng%ye=hWu3@FQzk14B*7d6}*~6;) z=rEO`_bLs+57612qa_=A-4O3;7g5|sZb`Re?7!G%i%5^|^sZZ6NS=Mw^|?0~T6YL? zo8h#3+uoB6IEP&CGg5&1!k`c~cmdwW&9$!Lo3A)iSk7%V;Fh6Td^EO{b2@S!EQ=lO z2aqsinn=d)dkEdESS$=)WsR#>TURSrZ@ey5f}8*yLfL0&M}*1m=QE~!`rcv5~2_=|2oH+}{_cdiApkCbkz24)u~mf{8; zbo{Q>+uH{Y#szH{FA0bM(_zVrWn{TMG8d0MTznMoTtB?Kk+xjKomMu`zeMIG7JS59YPht2$2(hGJ1faNMd=N~4css0rZ)~}8wuUc|oB))V=PKGw|ROZWgn*-nM zhXfDti`Sr@hiv+{qc1d>ec8=5;Rfnw)-pSHr-=1<)ukK#Xu`ATlp8Kd9RVE)#3v8Q z9`~Dg)@NX&7{gZu*<}B~b@y9XUb5WcWrEqzco`4QG6*ijJ{jOJR? z4bJk^CX3sP3I`>MOFAm@USf#sOHtMV4seig1dE(wGL^0Gr+_LV9*XIK&Sc-l%bU`w zmFjK{mbn?xy~0kUwu1x@P$}k1M7*<9RuxCQQq`bj{t3+GWA_Fy0fgKGX$mLtUnlHqccN<~+G<>_x!D zeyS6Z8_e>0Z_uWYM%j5bnB&aaCGi4<+Y!OF&AT1m-LFrp9Ei)_)=HI!o}Rys^9#2G z){z#&eY(EXFF;&uPB*yx4m17;<0mk~(xp{Ghtyl3L{sh$=u!uU5A0SP3`|C$B9m^) zyBa1yxHvGSzpd`H4YV-F1cH*LKD_gy+DJ3N#+Zv#Kfy-q==)<~YrnAMJfp}dbrl91 z;GN|0UnSA@r%XY_Vo3S=@Y&%~n_~Hf-qMbpk<+lpT3|(q&hV&N_@BfTWWQD`jt(^+ zVk=8Ddli^-Ue1uBN1YZd?d0bpYu4 zJwfCWev#QD-@jvU_Qr#%^*Ou`4rQt@S*Jc$_~Mea9ri|Sffx~ljNE06#UdR;a(z`0 zk_JlCveBp&y82j26?-ns4OMKczetolnr5(Y-!X``KWi;%ndAqX>5R(1#R>kY76{Fu z4rWIo%RBbX(S2O-7&&Y!WJO1kIC%zDI_Wb8RJwWmD)2eMSD5b2TQ7(cEpw(EEhSpa ze*Jbr7((h>$Q0&F-ppJaaWNT7?K3*Hr2^p=hL19?cxYUeIgk9!!NDDNVRix47PJ_3 z%ss^HIJ+Zvy#`GO(L>6)N^{UDXE*CCDG~&j;D`gl?^jO;k^WI>4PVfcEu_Y)3F9$Or?lh&96$3S(3_M;3qTAB=TU`&PltA(cZ_i6^`K2($nL=&z{Z*z4}GX zBHgKanEwdA)od5Aw7Q(q%$XgoJP{Z9uO8^OG*FIV`>G1NDsN1P(4eD2O@j%?Ll@y^ zFoP-(HxqgZ+Jz%9rN;9fdZ%$U*>Jk*`8~(ko=~&J9lJ7(el#l)o+;gE_AopO$v%Eh zjx|(qF`L85`)>tteoNAwANm+>b%u znG-2%qNO2n^?YYV%C%~RCCk}!0Ke4OVveaL0rrk}4bc4G(%8Fqt$NH%j)6oCvUsHH zbAJhiARUE{B2MV69coJZ0&YdQKDYD9iopaEZ|?(f`q!i?e22ah7GU%^(u{8P-3is& z7)s@gnKZL$+TIri2u8sQeCM(Ca6MqYuS`#o`BthkcFCOm`V#FGoP+i`I9(}d^9IpF zGOtB^7d{BF3B^x2u)}T7;LG(ZtB>b&n-bXRBji>#(*4^8xt4bCixFh?bIjp%nf@oF z5P-v~a|ZV+aq1>cxdYYZ)NiA(n;mQmdVoSwVcvFegsokdCj>nwP#4AQSExxD=vvPX zFep+9wHcCD}j={1WRNk(+odt)ZCrA3*>ydFjF%6UKzl_8V5#MH*jbk5~`uPGt;v>VpKvlQgU=vaU9 z_IH=qM$H^<^NvTW&CusT4MQ)ltv0&&KuPxq(&Xp0^YH?to7T)i8`P>NE-UFi+C~lH z?8~GCrY6v@&OzDCpdP=T-*Gz|&*TRN3Sw;_(CH8EUJ^2y=j4P-r)ApUoL2Xsq`Pbz ziyy>;!-5$iqVe11M#h?2KvD{8$}rzT$j~3jT3$F03m#4=~{yOExJ4wTR(FFO4BR&0&3lmz@e7^rq~2*SW1NHqv{s6^`-(neIY0_RsJ1m%}6o zcl3eBIrBDMx6ZKn?s;a{TBCk6#_Fv3?@9W!!=Q?*GEU?qTpN7ZU9i;r_x9DY9j{>DiU92rDX*u zgtQd(%5XA+nt%wNxAbvvxzNze)L>t@t)U5x2_pCwVQ~jOaCD=l7{bUDW5Iu- zrqRHby!44n5QGCm49pwE-s~JpO_WS^6j0BqdFh$qh^nq>Au~&UHqj<0o;Ea3Ye9xf z>^{d3>n6WRH9)EcB)(--`;ttj!Q4Yax@|YlJkJ0*?%vET>RAj5p=IQ8cZ7hVT)IrL z<@L*1(v&|uC!X$taM_(BJ|*zo6e1toh&~A(34z{2;!J-bOSLKiw@565uih5-LX_Pn zv&Cv<9so;yl=sFc z4u0LqGEbakgf@1*yV!FP`)EHcM{O%+ch4-1_jemdFY4U@-oQVM%DQ$0#-!FG@!;aK zhRs_lVTBajFGdb`V9Uhjh{#NNZ^d-lDv4(hvI4ch#Fj(*z){Cec8u@!nEa79$~A%@ z$p)Zsdy6G$;IcQrO`UbFRsrFb?=+S z*3%?zuCl;FpD+}}Z=KQ^x1y$Wx7vq@G&PJA!uhL8Whq=-2xWKJjV~l0v44 zA`~y8_W8>N&_?fhF8Q#gZOvFGW7RhS2KnNg_k_g3D+D8JcDU};>^k2XuWw>@6euYU`VM)txbk2?;$g_7PDKIU#tVk9VK8S z8Xf^OsUWC`++Lqrb1577Rjt1!oMFI(I(5rL%{#>Hn$UhPZ@BbeC!C5W2&Qd+J8t@F z^55|{A3rLrdkbDV^tDI7a1bSK| z4L#Tzo*$s`2HV#dz#XI`%mg5J6LY=udhJ|@%t{dI$I`#GP5<*Cg&f%d^%k52ka9VZ zH@_k;cypo7KN54@PQ37udB5j{Q!({Lfo3tM4g**HLyj(RF`HMHFD;s4GQ z5mU?z3$$oyg7+P`OdVTtpe+ybU0jpY-cZ2bd|KenwpZAY+5?(eB*6})>%Fo_EIoLG&s>jZb~Nia&^QM^@D`85OqasKXI9SDp-8o#=Ict>JPh-po9Vh zTajAG3mi=j6%w~B46~n*l3;O+V zN=xSsyy!ycT;hV!a64d80Wq;bBJS0#H{Xsu!nr3m1p12KdvMYYd_E7*io{MF=oJF; z!iEE#7QR3H?H>L{&^X+buzSqD*4zMqM9Y!32~oLU>vNEo@IF)?OO)FE!vAPOyul$} z{5mQzUKCjx9j}WXHa#P<`nV-uMwG3P;>7Jae_{ePOTRoVoC-;v(sy&KdEaG2qRl_r zfiJTo-9ed$W{OlsFehA#QQo~K)oXJ6>)GzO6XfUn#JSG(>+LbEV`z$C3BDfc{eJTs z3TBrEB&^7##iKxWc26yf{X`cxg1Lyv3P44-AW+2^_d1;$qTqeRTdOCI#C|8shsbp& zZ7=)?e zsWneue?s6@#a^E#k{p6F8qCMfAoY8}cE^u@gy=_FK-h4aBWYz3pz=c^8sZosfX;=exL9R2I>YiL+ch_|*s5Ce`4I}0&x#%4M z;%avGrg6@i7koBNIyo>I&v%`2D9;W?p%Uyf7fmdul_9Awc^)w%XvjAJh?nW3Gt)}R z8K!IsaV>boes%HbuX?9qul^_l2pO#V$aAGEz1i%;*g`cm)rmrNIwT^L?0wg1QckLep24_PwxQ@OIal|& zq48q3JV@5@Z%D8Ag)adrY!-fsC!PCdNe0Z}y#kWf??|ZcMd2-SL7Ip3ckE_e4U%p- zCvCpV)RbD@X8;!~eD?x|(;bHmE@3GKnh&%nkZX~Lq~VO1*tOy~-ECT3Nz{{;uhm3A zFixqpTC9=rg}I%9wGz7JLy zFR_*`K5b&Z+wm>54Y{}rc+9(Ch2NBho1qZ)>>?dLm2ngIxY_xCM?BrKh=MFnfLJCstWx2#4g;t6z#{}o5=WZ0^q@_)bT7Lq|fffSG~g zLYIgoPYN#NfqY_cN5$`xo9UNBA2DcF$Fb4MM=CmWl zYk6bLw{|MFJ|586uJOiBjnKAq{%COzgFJU9%QbJ{{Gx)B5-cAw$3|K>rm#^1@aL@Q z%5Mmj3-^Rh_ulWUCvYUG3Crj5nCRRx)IkCEHPO63;yFiJHyp6B^BDDe9HTC9`8amM zs)YxfeQY9nT|F+9Nx#kurA0C{*dAttuw_ms;C-Vbx%ts@AHp^l=0C9ip>MUP;HEyU z&wfIOR0|`jLS(q4TjnX4G_^b->w3wJKQPkhj*o6T5o)(pkLDs5?#DVhd|U?mC0*X< zC>WY>A~MywEA>nRO$-^O$F4mkW;z>A5xt&kO|EM;2&hU<2l&S$O}3yr&`g^5c!1vk zm;Q2Sv495j-9y+4BpDlFTcbG4sR6M-B0cF#Ihsu)eL@YO=VB<{QRsB{Bm`WdN9d-* zUvxH1Uq3wNV|u_CEM1Qz_M-RwL6)}UYo$QOkxtjhYABWn1)jMz|J0XLsIw8uCB1Qo zQ)+CsV={<$5CFIk`qMH6>R$okyo5&?^lNbNuC0P8;Xu5(jKE9)G8VDbFP=TzU=aMg z2T|c#K&ToqdiU8CS!6~iD}o5&D+Bs)RzA1PQ1y14Q3ns<`yZvJor^J1Xl!0pd>rj3 z{kR5^A2H&^G&7ymsy|0xWaoOt0jB5?-RM`y>kDKCi$6} zjQodGlxQsE+ega#YRaaCl0_wz;nocxHasOZY3c0ZbAyfbR@H(gBg6Ek)g1Vr3i3I1 zqej7c{1taQLtYIH;~G`Dt7WR6F-k{(XPg(n?5i^fPE%i{m=X%3(t;<_3)K`f4Jev| zpO@?pAd8LMR+l(OrMGyogt*TuAb<7xm=P}FlJ+wGmx{*Z!J2U$HQmQ$i_1(_nA>-? zL}V5g)4iw5JR2-X{`=YWJJ1-xa5#ftQQ)X0j=LGLJnzME*FJV8)qxd7k$VSX8NVN} zf8$hi`P1=p|NbH-WEfDN-LguU&c1iqx9#M;ea(!Rf^9Qwu@X?=g@jvDHo#+iAt*Q< z<*q57DQuzM?>+B|6e+?m61NcWP#mI)|K^vtCn@j0#qG`oI{urSEj1A%VYdJ55GOwU zNE|wivpC{SsOmg+c6_5>)8Gx)atpoSlJ+%=vY1phtf_3!C|5En>l>$Ul=Y~?^lVyC z*Ijt_-VyX1uzK>-McZF_@V8m!dvB^n-PRKl?eWtGzgG-0KWf|Wo7wZ@V0sk|2&hA` z4Ty8ipbYNYu)uh+)<+h#JOX{LxOtNuZ=ySWJ#mK@mRumsR)CJ!!=m*xH{ho4_vr$O znBZLwySCLB$UinlU^|hNI&uwMTldRgrN$g^mE;F_x;JSbO$H{DXppt4V#i!4oh@ub z=6N122KqLC#b3DOpICeVCW!@0!vsk2W66wNxYt(hn6&G%TGkg^{heIb6N;oSJ=cN_%#y?@ox!QzmSQrWy%`dMC``)?Lstrz$k&81fd_> zNd=wE0|A;}O3hb~=<)K95p>g4Sh%3Gks$~^3BGEp#Wk6@qcKS@~t>@~K!wKo=N z#;N8-R)11QqxH!up_`z)nrxU7g;i1^|1j|DnwlQR`yigfp+|JVCVqxl{}~&Nsu{f- zg+AYuhb!ASjs!)dcypy2c9gygZExF%u&vNr``*h~L~wGkf$(_+RJ}w5u%PL)P^ci* zn~lqQ3xC!FgBpJksmdy0yT4~2NVRsfmv}M=ff2I&R6BWB@Yf5cvM`g@afT0Hydmi+ z1?4O=WgVw^JLKNtbfPHy8KnDCZ>9RJQ(xg>LWO%XXIGA1LV#f9yNe5^)_y6$*D=-{ zPhwq5tkW76eFF)W*%_N>5u?>FQ4u?d8N_(WkWI~s@B!vH!N7IFWNDVxHR?KP3b_Xz zzheZOnjv(K!^;Xgh`oMaS}QDwUlS{LH-XSROv35Gf~xY26hgLwul^E>McB7##8>we z;?&8=e({XRA$OGAb)J&M#)dHuzTTK(Fnn~v%xsb_HRY4hDA49FL4VSq>OuM7I+iGA zm3};5d@4mx;PaZ@Y>wV9K(`}?_$-mZbd!;Jip(ZrhufrViAPxlT^3?=Zv?+tZ%p+3BJ1RibUf!>c#z|-qHg+vcHkooz3a)_n+IE+om;fr)E z;Rn5W4vcm7o{({QIZ~o||CDXAtCLYk9po_p6~tpt@r)& zt+m%WXRUq8-uvG7wfA-JYu#SG)eo-T^-G6)Y;NYjo<}t)SszW>c4}-lJmpdIl}F)` z(kF`R!O6cqI#GAG)W}`_4GUwWBQjN>&IVYh?Vjh?sK0Nyra5mHdEQl2*Yb79++f9* zwshSxK1g#6W>nG|`ZB0v*UqarGusu4!0rHbA=9a28%);tg{T=!i$OEKwe+1i(gY-s zY-VHFLCB|jdyUB5S;IDIh+(Q{r)y4Z+~LY;{-M;zMc5S=M17xYBk7>h)r#%x(`gs- zod*tnW9XNi{~QQ?>hD9UF9k(X6P>U-Tj#zc!BT&Zgr4~J^zI8w;M46L=+D)|19dT{ zXSBqjcX6?`_ex1WGr|zYmKdCYi`zFtM}loNDSO zCgRBu?9AsCod`b_9qxp1QLoP{?V~)?dOF+v0)+5e%yj<1?UgGpAhBz$WKZwDkM@h# z4r}$kuk;wRE&t{GcEy0E6z|#^;o|kgG&N`MoWA7kk;;GitRJrXZo48!*!k!4;zwj1 z__e1l);R>Dm4WXayNQz{^t^R^7P}enVtY<${yREqPMwoqi42y(auO%&-4^p zVN15qD3`hcWv`jCfOmCOKZFYm$f$J35iW7-k=5DFnR${@T*TLG3}>yg`U-l& z@7Dc5qpl4%evTgd@vj4CA z#yB&NN9Rg^R8KK8brY;0{&5R}n}{68O<(agT^0(M=7bDqnqvC1BM9x8(?1u45crjY zIX30bZPxZUr`7-i?GVn=eVg5i#VeQAU?-j9!_>gq7T2EFL{^~JF2!pKe))`8Kk~`r z3ET6Bo|#G4iX61YZvFD`Ajw%v@wWYKlM^@Bnx&cM3(FwZ*WckhpVs z6_l`y8jX`-agyodD(o#_Lg#9JOzNOkmMP)PlFhLvvsn83)|cCE=FVNZR2YD4JAug~ z!%Svnm27|jTl&{V z<%g{f+$(TIxXp2;qqqE=AcUKlI2GHbtlx!=AvVBzXx0hIO~XlMv>?LH{||1xGC$fjAm8a<)rJW|E}cf!Z$^mu7MxO(z|B0do$U{CB{m)F zv`W}${2eIa`>(o)py?meD2p^9#`h|>XlAqT{wBY??vmie#(3*{cNvB71(pUqgceg* z84<7kRvxyc0KmfnSJ7e|1O*7<=k)reYs=GurntoVPX==5%$wAiM&|(RQSOt*I>o?3 zzYB_#RoMKyJDLVgc4|Dhj$Gr;t-+Z?bD{m&sPg+;LQAbi|>I-;`EA+A;zW2^TzDRap~b^%?b#-&p3m5 zX5e%09mNg~p9tYPNa)JFCM5@_jaW|pE{HyyOw1~0K0=wM`c^%Q=`FIt=E>g=$?JG7 zUc3ASOljvF)Iu0+T5V6dRo@q7DD3nno)sm5obpc+^UTjYX1&@!)5P3VkU5U(*!^CaJjd=P)r4VWKMzzs#dN&fbkR5DjP6~;MO*j`;2e4JFk z)o}BH*GRmDm3F7;LsonJ@^zwOmL>633l8q|K?$&(;g$!{2HmZT`_onuN!lXyEsVHV zj}vk>c`=LnAG9kp1gqac@uqWp+F*Ca`~qg(L<8K2p99COcIV1{sra^XaHR`qUA($4 zy<)&CZ&MW{Du^2lfL-cu_liAoGNXsOn%q@K!i7IpkK^1&G}xX(P5N(EV2^Fbm=sm8 ziCmb9%GB|GzrS$OmWOPEZMjhB1Sv|Hg^nDeT7UprkiWi1EiGnF@gC|a| zBsp05f}1Gq@75bmkODV#X}bp+X~mYjW~2n$1!tkHIG2DDzyJ8=FRjf_ggJl490+;i zrdwdYH=3#jFw}=rPW=(C?loJYE7;1o=@}cp1GQyJqlgeYbaY&_nk7~?DsiE_ez4|P zwB!;9rD$wV_LcpFZ?CE6e7hBEt36GreXo}J0-IsMTH%j8`|t(YbfZ&z?* zxm~w<`eP`u-5tEzhim%ZV=q^G%-1a_AHpi@@JwLp}U2m{BA{9pA=2*2e55Z z6a=OQRNkYArrnJeir2mQp*49 z+Y@)eKhS#Ci9d1o6PRl{nRiYBg=691AK0ITBwTR)wO+3T%hi*Pu(qOv^Cm!u=)Za* zD21$+41sWiknAyv^^E0PGHCUZl!9=?_0k)Z9*`aju`<1-+v3P z`j7J*pBwXmc$j{%%T4X3XlTfgr#5;uh}C-r&Ht44lg}vx7xDv|gDs!S_3#aK%4`2% zj|@sdtp$%Ng)-wNY-IQ_E*>izH5bD;6kBWAe>H?h($7-o`Mitzklq|lS8P}=gn zxOOMEkXNNM{Y=jCYZs`ggD{wOi#0t%x^ml}+{Kysp3q=qbWlDlBg)6b&Fn?kJndL~ z+jq=8Oja{AaE!h&wvXv8E)vDz7k1ix;#BpB2xyuwNGpQ`Tde9b!fE|Yn?@sIP3wJ2 zD*7qu0G>>{i4uSv>}iOI=;Y39v=Y`{8}VNuwS#KnTulNrPy+cm2dyJH`g^p4mzifvFqMUqw3-L4EF!Vb4(*Xit zwH|7x`3;(tf;jZT>q6$nl-awquKTU4ELFKkxtA5@vbhBEd8@u(;YWrZ(;|i1X9Jc8 zy=1D+w|!sy5Sdk3(gnL>C)Ine6SL!(yuFXS_;!)=z;3O)vEzQj@5>2(cou#+a?~np zj}{QftMM8-H%@hQ zVedWh`r)0mDEoEC8|K!lK0nY>9rd$iXF5Z}RXJW+=p= zPwH1>ajM?QoB5x|^_6{|`n5^wE{!AtGOMVrX>Dl#F~+CqcU>t6GK=7CN8h|GREzzT zgJToxH>y#2)A4#!4w<- z-}+-~eLj&GOewBTa|N8L4uqN&%kI&b0j$C``jr8xv-(n7x)O=Vj*TLirE%WMn zVtq$IlYbj|t>N8*k}r8IM15rO;)|G9%{qbk4!(tZIHaLJIWiHa{gN4_Yzr&9EAJ~H zIUATz7QSrXkcFjS9}o6t&4Gx0$%ofdx2P~GpIDjG@|@Z3(+V=!7+kJpCGmje(LxDf z-pIZXw)gDkS|tNT;qMu_s!>GkJ62V|af1XB+V7Qbw@aO4ALe8v+OBTf%e+|Ymb!R) z`cFAa+k(}Vm6xQi?f@>T+^{KpWFj;I9DX^RPfREatB%O7-l#Pdd6QMV_}9SmZgv#r zdLW859~e{yDkRSt|8he1Qz$$Y7kzrdzr3+fsgy0>A7@fF#Hz_X&UQn*WEWp4IkKXx z(^N}1Te7!ELZJFHw%3f}kQaTVE_@2IECeOl3j0jf^sfQ$M#v)1!QANbUrPK6MA+-6 zz+CLT7xl9Aed3Thc2YzY#QcNmV0VeM^DJ>^RUJEaq*E4EBEfYpSVIQvp=7%G>1tpF zao)V9gXCWruBsFN9f$#2tXTLJ!u(j&yNYZjPX-z_=?@w@O ze7rg}xq5;RHk_0Ie{WIQk3*$3&jSCb;gtx{!RC)kDD}(L&oJ2Q9u$GDQMc1Hq_8PP z#y7Khi;tPu`N0~6jqLR<%?)t^w~0N3cUX1-3dnZt>1}GQdY0Iou^$`trO8H-J&1Jq zX)H<$S`f1od)U1!wJ3al+IQ$r?y>Ty^7?OJZOJJ#$MAF06ITn5+UH$h$P0h6{de-~ zm15H~2D0N{F}H@35Szc)JnSnrKcSImx zQ1=8|T*$p5JytW!Z0Y_vvE3b@1$ObTT~&R*@aXq(u?FV`>egSyfrhdZsZyQrWiLK? z7PWR}HM2f>BkpSCqY_sMiUMwG0Cv(|GcB`QIBTe4@1tZZ2`UIgdnY7<0{K-zbSa$e z%+OZ0o1@Q-d@Vr4U!y!G85LG!g`2e91r+azJyr;$*dY3SKBx9PA(kwpc@vu`77qoF z%fRcQkbh9~pGEF(KCvYDxjX**h?tl16}cBI;x|1o(kyw0_)P8IM_H{oZ(hW5qP};sump?1Xp97wnazIx1CcE=n%x}o z6oC}IV3!RhR9#L@^O?hO$y$|0Y4HV3 zY##=ySc{Q*Kj*4fEjgC=tOu@tZY9s^)0juXKqoTN%Kv@y23SG!<%Y3k#>j|FFHw+z zpCKlP05bRUIX7Lc3}?j&dHrQ%Sf%5rKf(#m4x|WQ&!#j{llha8j76MUU>D319k3Q5PU_vS>Ec zvT0^b+aT}TsgNY*^@Z!{1mfxAF%PV4f`?0)n^U+M}QP{I}tG{2Ce{%TlU*{j*dc0x7 zFIE4I;vV?lqqtPl9k#9)_Fp`xefZ^vSHGp&UbztdDm!+CEq9^Q3$I3vGr|teCwiJix<@zzMm@X+8TfDMMCL_8*i7C~#4O1SiHl;|(MVilgN zecXUVS7M|%N_|R3$z`Jv!Zawx`_480!Ll3VOC$_nU@k-f5+TKS)d|$Ad%fe8nc+w&s;egw`LVK3#o5_YZ5mvje;mGX+FfYWVhg=7eeF`v;4)y}Ty0M4MVN}Ye@q=sBMt2C#V6*md*{qQ)zb;w~@6+^y5^z(@?WW1*FbEnE z@B5-pAK8KEgRVz9FIgbvYUU<=R-ty*&xlCW*$fFG4T7Lq z+YrT`xGyBy8?Kg~WW&rrCwQ#MBLza{i?Ykh8lUspsEQ2H1am9zDRp~&TfGt#Xn##0 zFU&aQbY=0b067$43jk$#+e5rhBk~}6(pdf?z3NBU_rVk?!qToIfNuXNop2KIq%l8+ zz8p8}0H64%uI>i3oY?VqaSrfiWlJor3Bf*A{6CZf2pZLh<%-MA&*hpFP%V;{;Naka zdCBC^_PL$JM~tyAJiRl13o9Z1!H^#AJG?CtW}x3z;iqR?W(( zxhG7J;n|7G13jQiyu5rTH5Ki}L*lZ46C~AcsMloLaGTr?ka3O6NkC54`5!SLZ_y?3 zQ1^WP=A_$`|&9Bbq1-?%PL&9Jv&`F4_XRZ0lP}EByYe-L-yD+Kiz#3S!|A z)*iKb3L{aAmnxe9xk4k0S~#rPp<+Ia z^qBFBS`%dA&lFTLLaF*aJ!ikCkP*AsR-G~m?SatfrL@mWf^$pj!ffF+YTA-sq(KIR zRB-Gi)tk)5O|rH#xMgCnyI*3|Y+$r4PGh}RZMW1i`Z_~-Wv5$ zckKzgI6r({r)HtLm>)4~E~AaIk+0RePRZ9BHhC^N7Ee+sqv5k%`xo=aMGZFiZ&h^K zD$ag}W2Q03mJGckbk?S!tmVyd;a`oI3XEU;y+QxusD-fmr2%2Amb2C~ksm-;pz@_d z+S)GSHp_AdV~=I{U&-I`;RnHQ)>Z<>xFxsIn253vP7@1x(GtNNF!rD2Dt z9@|R^5|)AHNDbV_?tzh9Kbg>XcjVi`8K?G};VV8XVm*jtWQ~u-Z&U~r2uEiwHt-p# z#(_Oid9?3R!iw8XS0q%J59IpQ3EXx#nK&I|CRpeQUCB&4p5*rg$Z*i2MkUxMs6GxM zMxiqW9!RnwIik%G$QZ$Ot)qb}UuBC}xv?zN|@J&^; zA!@4Z7%h*>s7GH~VAbraH+_wMp=CXl>S;5T(YTZ3QY3bf?!TEkIU31BW4N(#PvuxQ z1=?wZ1$M1}mwiHdatf5Bcwl1eBm+HoxzTu6$hJUSpq?QynswP{j;AM7h|J&y zU_F=AcAuYON9mMjm;HQZw6+%0&mIi+K~6CY5!uFzh#wV%nx7o)I`y$|U#A9f)P$O! zN{^u8ZW1!_Pz^g?j%9(2)MtRp3Yxhv(D<0m{a)uqYe zOI~&OYl7mg`KrIJAE|lq>tg?hP_yL6je8jBi`eQc)j>R2(~0Z`*O~k320|>@(!IiH zG-K@6TckR4dD5{aJLw#TVRBsGi&3be12rFj1zPadOuG>ukxXzr;c-p5PD{$fV@JRY zl{-291(6n6tz ze*lC7oN4SGnx@pQKs*v^JV3u^s!E%fXdXhXG(~HqQ@jg?BWkD|Zf%SxIb)f&2!&)^ z!Nt(n-6#0oiDJI|%tDVbLxUh=f7mSj#@&?JpG2BS zXb6?^%&rjK-VOp@sM$426a9=X93(IEp0^$ncP1czSfdRR7-Lr`1)NncMi2IUmPUyk z?e#)djRPdpJjh+P+-JTXzYA=3hE}W*2m};C^NMxO05#xaO4&QdQbPYtjEJ~wRV*qR zUh=brTk&CzA1@;2%k>#XOC!?|qvxn<43N{4x2xqivA|>`KQ(C-%gi5ZuQEjD!gJQ~ z%0tvzIuuOafYwZI1ZS#)%Z>t14X0uD7UbAXU0A~&C?+heI+L?CxA_pwu0k5Dky_T( zcncXUXsEP&?%js-+(QiwvBCg;ed07$NX23=)^1(ufu|&~$VQh%IeteGotDyB+Ud-C zH;H|^OYwM-kH9hPYg4 zE6Os6n%S>{!1;z`41MyAW`*|L$za%@^v{S!`DC6<_Q`L)=ZLNdR_mtYnVuy3{yxrM9ZiR*B(V0scU^AV(Ohpz)05<~QA+$f|`s9b~ehcgfA7< zM1sJ~%no)Okf09XT||6!&cK%1c(GHd=cnB;=(k_C&8B>uqYy3Y34E^E+*Z(~o-Qn^?|Xysy~M_0fPkI2xE z6g*{mg!=WZl;vHIA0r}`beYS&!NNHcC5B+dmNGB?vng8R`zXg!d8xNY?PkXsek!4Q*H^h1Ozl1nL-=98o-nGS9u57nl*)El`U4P$1 z);)OebmWNeev8?Xb8tJp(22bFzy0qI)K61%G)<5RzsqdO21C$dZ_27FaXIHBGtvR= z)s&I-ws<``oo@0%(G4XeBC)n+#xpg-(4iz9hWjkBCrjY+f=m`1d!!1`r^IjQ11DoM;@2w8eESt=MEn-zpk8BS(*CE z$MH5SuDtxNH2U7X$g)zuU>O;Y96e+7_wC4vhY`((#61=oxWvSiwo?q`Z6D& diff --git a/src/icons/tangent-arc.png b/src/icons/tangent-arc.png deleted file mode 100644 index 0db8ac3a7fba8d5063089855bf182c076068aa07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27250 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 hX@@G9j-!`e$~ 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 SK.GetEntity(hr.entity(2))->PointForceTo(p0); SK.GetEntity(hr.entity(3))->PointForceTo(p1); @@ -139,6 +152,7 @@ public: DRW_Block data; }; + bool asConstruction = false; unsigned unknownEntities = 0; std::map styles; std::map blocks; @@ -205,11 +219,11 @@ public: } } - int dxfAlignToOrigin(DRW_Text::HAlign alignH, DRW_Text::VAlign alignV) { - int origin = 0; + Style::TextOrigin dxfAlignToOrigin(DRW_Text::HAlign alignH, DRW_Text::VAlign alignV) { + uint32_t origin = 0; switch(alignH) { case DRW_Text::HLeft: - origin |= Style::ORIGIN_LEFT; + origin |= (uint32_t)Style::TextOrigin::LEFT; break; case DRW_Text::HMiddle: @@ -217,35 +231,35 @@ public: break; case DRW_Text::HRight: - origin |= Style::ORIGIN_RIGHT; + origin |= (uint32_t)Style::TextOrigin::RIGHT; break; case DRW_Text::HAligned: case DRW_Text::HFit: default: - origin |= Style::ORIGIN_LEFT; + origin |= (uint32_t)Style::TextOrigin::LEFT; break; } switch(alignV) { case DRW_Text::VBaseLine: case DRW_Text::VBottom: - origin |= Style::ORIGIN_BOT; + origin |= (uint32_t)Style::TextOrigin::BOT; break; case DRW_Text::VMiddle: break; case DRW_Text::VTop: - origin |= Style::ORIGIN_TOP; + origin |= (uint32_t)Style::TextOrigin::TOP; break; default: - origin |= Style::ORIGIN_BOT; + origin |= (uint32_t)Style::TextOrigin::BOT; break; } - return origin; + return (Style::TextOrigin)origin; } DRW_Layer *getSourceLayer(const DRW_Entity *e) { @@ -341,7 +355,7 @@ public: hStyle styleFor(const DRW_Entity *e) { // Color. - // TODO: which color to choose: index or RGB one? + //! @todo which color to choose: index or RGB one? int col = getColor(e); RgbaColor c = RgbaColor::From(DRW::dxfColors[col][0], DRW::dxfColors[col][1], @@ -353,12 +367,13 @@ public: if(width < 0.0) width = 1.0; // Line stipple. - // TODO: Probably, we can load default autocad patterns and match it with ours. + //! @todo Probably, we can load default autocad patterns and match it with ours. std::string lineType = getLineType(e); - int stipple = Style::STIPPLE_CONTINUOUS; - for(int i = 0; i <= Style::LAST_STIPPLE; i++) { - if(lineType == DxfFileWriter::lineTypeName(i)) { - stipple = i; + StipplePattern stipple = StipplePattern::CONTINUOUS; + for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) { + StipplePattern st = (StipplePattern)i; + if(lineType == DxfFileWriter::lineTypeName(st)) { + stipple = st; break; } } @@ -406,14 +421,14 @@ public: hStyle hs = { Style::CreateCustomStyle(/*rememberForUndo=*/false) }; Style *s = Style::Get(hs); if(lw != DRW_LW_Conv::widthDefault) { - s->widthAs = Style::UNITS_AS_MM; + s->widthAs = Style::UnitsAs::MM; s->width = width; s->stippleScale = 1.0 + width * 2.0; } s->name = id; s->stippleType = stipple; if(c.red != 0 || c.green != 0 || c.blue != 0) s->color = c; - s->textHeightAs = Style::UNITS_AS_MM; + s->textHeightAs = Style::UnitsAs::MM; s->textHeight = textHeight; s->textAngle = textAngle; s->textOrigin = dxfAlignToOrigin(alignH, alignV); @@ -422,8 +437,9 @@ public: return hs; } - void setStyle(hRequest hr, hStyle hs) { + void configureRequest(hRequest hr, hStyle hs) { Request *r = SK.GetRequest(hr); + r->construction = asConstruction; r->style = hs; } @@ -454,8 +470,8 @@ public: Entity *e = SK.GetEntity(he); Vector pos = e->PointGetNum(); hEntity p = findPoint(pos); - if(p.v == he.v) return; - if(p.v != Entity::NO_ENTITY.v) { + if(p == he) return; + if(p != Entity::NO_ENTITY) { if(constrain) { Constraint::ConstrainCoincident(he, p); } @@ -474,32 +490,34 @@ public: hEntity createOrGetPoint(const Vector &p) { hEntity he = findPoint(p); - if(he.v != Entity::NO_ENTITY.v) return he; + if(he != Entity::NO_ENTITY) return he; - hRequest hr = SS.GW.AddRequest(Request::DATUM_POINT, false); + hRequest hr = SS.GW.AddRequest(Request::Type::DATUM_POINT, /*rememberForUndo=*/false); he = hr.entity(0); SK.GetEntity(he)->PointForceTo(p); points.emplace(p, he); return he; } - hEntity createLine(Vector p0, Vector p1, uint32_t style, bool constrainHV = false) { + hEntity createLine(Vector p0, Vector p1, hStyle style, bool constrainHV = false) { if(p0.Equals(p1)) return Entity::NO_ENTITY; - hRequest hr = SS.GW.AddRequest(Request::LINE_SEGMENT, false); + hRequest hr = SS.GW.AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false); SK.GetEntity(hr.entity(1))->PointForceTo(p0); SK.GetEntity(hr.entity(2))->PointForceTo(p1); processPoint(hr.entity(1)); processPoint(hr.entity(2)); - if(constrainHV) { - int cType = -1; + if(constrainHV && SS.GW.LockedInWorkplane()) { + bool hasConstraint = false; + Constraint::Type cType; if(fabs(p0.x - p1.x) < LENGTH_EPS) { - cType = Constraint::VERTICAL; - } - else if(fabs(p0.y - p1.y) < LENGTH_EPS) { - cType = Constraint::HORIZONTAL; + hasConstraint = true; + cType = Constraint::Type::VERTICAL; + } else if(fabs(p0.y - p1.y) < LENGTH_EPS) { + hasConstraint = true; + cType = Constraint::Type::HORIZONTAL; } - if(cType != -1) { + if(hasConstraint) { Constraint::Constrain( cType, Entity::NO_ENTITY, @@ -509,65 +527,102 @@ public: } } - if(style != 0) { - Request *r = SK.GetRequest(hr); - r->style = hStyle{ style }; - } + configureRequest(hr, style); + return hr.entity(0); + } + + hEntity createWorkplane(const Vector &p, const Quaternion &q) { + hRequest hr = SS.GW.AddRequest(Request::Type::WORKPLANE, /*rememberForUndo=*/false); + SK.GetEntity(hr.entity(1))->PointForceTo(p); + processPoint(hr.entity(1)); + SK.GetEntity(hr.entity(32))->NormalForceTo(q); return hr.entity(0); } - hEntity createCircle(const Vector &c, double r, uint32_t style) { - hRequest hr = SS.GW.AddRequest(Request::CIRCLE, false); + hEntity findOrCreateWorkplane(const Vector &p, const Quaternion &q) { + Vector z = q.RotationN(); + for(auto &r : SK.request) { + if((r.type == Request::Type::WORKPLANE) && (r.group == SS.GW.activeGroup)) { + Vector wp = SK.GetEntity(r.h.entity(1))->PointGetNum(); + Vector wz = SK.GetEntity(r.h.entity(32))->NormalN(); + + if ((p.DistanceToPlane(wz, wp) < LENGTH_EPS) && z.Equals(wz)) { + return r.h.entity(0); + } + } + } + + return createWorkplane(p, q); + } + + static void activateWorkplane(hEntity he) { + Group *g = SK.GetGroup(SS.GW.activeGroup); + g->activeWorkplane = he; + } + + hEntity createCircle(const Vector &c, const Quaternion &q, double r, hStyle style) { + hRequest hr = SS.GW.AddRequest(Request::Type::CIRCLE, /*rememberForUndo=*/false); SK.GetEntity(hr.entity(1))->PointForceTo(c); processPoint(hr.entity(1)); + SK.GetEntity(hr.entity(32))->NormalForceTo(q); SK.GetEntity(hr.entity(64))->DistanceForceTo(r); - if(style != 0) { - Request *r = SK.GetRequest(hr); - r->style = hStyle{ style }; - } + configureRequest(hr, style); return hr.entity(0); } - virtual void addLayer(const DRW_Layer &data) { + void addLayer(const DRW_Layer &data) override { layers.emplace(data.name, data); } - virtual void addBlock(const DRW_Block &data) { + void addBlock(const DRW_Block &data) override { readBlock = &blocks[data.name]; readBlock->data = data; } - virtual void endBlock() { + void endBlock() override { readBlock = NULL; } - virtual void addPoint(const DRW_Point &data) { + void addPoint(const DRW_Point &data) override { if(data.space != DRW::ModelSpace) return; if(addPendingBlockEntity(data)) return; - hRequest hr = SS.GW.AddRequest(Request::DATUM_POINT, false); + hRequest hr = SS.GW.AddRequest(Request::Type::DATUM_POINT, /*rememberForUndo=*/false); SK.GetEntity(hr.entity(0))->PointForceTo(toVector(data.basePoint)); processPoint(hr.entity(0)); } - virtual void addLine(const DRW_Line &data) { + void addLine(const DRW_Line &data) override { if(data.space != DRW::ModelSpace) return; if(addPendingBlockEntity(data)) return; - createLine(toVector(data.basePoint), toVector(data.secPoint), styleFor(&data).v, true); + createLine(toVector(data.basePoint), toVector(data.secPoint), styleFor(&data), + /*constrainHV=*/true); } - virtual void addArc(const DRW_Arc &data) { + void addArc(const DRW_Arc &data) override { if(data.space != DRW::ModelSpace) return; if(addPendingBlockEntity(data)) return; - hRequest hr = SS.GW.AddRequest(Request::ARC_OF_CIRCLE, false); double r = data.radious; double sa = data.staangle; double ea = data.endangle; - Vector c = Vector::From(data.basePoint.x, data.basePoint.y, 0.0); - Vector rvs = Vector::From(r * cos(sa), r * sin(sa), data.basePoint.z).Plus(c); - Vector rve = Vector::From(r * cos(ea), r * sin(ea), data.basePoint.z).Plus(c); + Vector c = toVector(data.basePoint); + Vector nz = toVector(data.extPoint); + Quaternion q = NormalFromExtPoint(nz); + + bool planar = q.RotationN().Equals(Vector::From(0, 0, 1)); + bool onPlane = c.z < LENGTH_EPS; + + hEntity oldWorkplane = SS.GW.ActiveWorkplane(); + if (!planar || !onPlane) { + activateWorkplane(findOrCreateWorkplane(c, q)); + } + + hRequest hr = SS.GW.AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false); + Vector u = q.RotationU(), v = q.RotationV(); + Vector rvs = c.Plus(u.ScaledBy(r * cos(sa))).Plus(v.ScaledBy(r * sin(sa))); + Vector rve = c.Plus(u.ScaledBy(r * cos(ea))).Plus(v.ScaledBy(r * sin(ea))); if(data.extPoint.z == -1.0) { c.x = -c.x; @@ -584,17 +639,20 @@ public: processPoint(hr.entity(1)); processPoint(hr.entity(2)); processPoint(hr.entity(3)); - setStyle(hr, styleFor(&data)); + configureRequest(hr, styleFor(&data)); + activateWorkplane(oldWorkplane); } - virtual void addCircle(const DRW_Circle &data) { + void addCircle(const DRW_Circle &data) override { if(data.space != DRW::ModelSpace) return; if(addPendingBlockEntity(data)) return; - createCircle(toVector(data.basePoint), data.radious, styleFor(&data).v); + Vector nz = toVector(data.extPoint); + Quaternion normal = NormalFromExtPoint(nz); + createCircle(toVector(data.basePoint), normal, data.radious, styleFor(&data)); } - virtual void addLWPolyline(const DRW_LWPolyline &data) { + void addLWPolyline(const DRW_LWPolyline &data) override { if(data.space != DRW::ModelSpace) return; if(addPendingBlockEntity(data)) return; @@ -622,28 +680,28 @@ public: hStyle hs = styleFor(&data); if(EXACT(data.vertlist[i]->bulge == 0.0)) { - createLine(blockTransform(p0), blockTransform(p1), hs.v, true); + createLine(blockTransform(p0), blockTransform(p1), hs, /*constrainHV=*/true); } else { hRequest hr = createBulge(p0, p1, c0.bulge); - setStyle(hr, hs); + configureRequest(hr, hs); } } } - virtual void addPolyline(const DRW_Polyline &data) { + void addPolyline(const DRW_Polyline &data) override { if(data.space != DRW::ModelSpace) return; if(addPendingBlockEntity(data)) return; - int vNum = data.vertlist.size(); + size_t vNum = data.vertlist.size(); // Check for closed polyline. if((data.flags & 1) != 1) vNum--; // Correct coordinate system for the case where z=-1, as described in // http://paulbourke.net/dataformats/dxf/dxf10.html. - bool needSwapX = data.extPoint.z == -1.0; + bool needSwapX = (data.extPoint.z == -1.0); - for(int i = 0; i < vNum; i++) { + for(size_t i = 0; i < vNum; i++) { DRW_Coord c0 = data.vertlist[i]->basePoint; DRW_Coord c1 = data.vertlist[(i + 1) % data.vertlist.size()]->basePoint; @@ -654,38 +712,38 @@ public: bulge = -bulge; } - Vector p0 = Vector::From(c0.x, c0.y, 0.0); - Vector p1 = Vector::From(c1.x, c1.y, 0.0); + Vector p0 = Vector::From(c0.x, c0.y, c0.z); + Vector p1 = Vector::From(c1.x, c1.y, c1.z); hStyle hs = styleFor(&data); if(EXACT(bulge == 0.0)) { - createLine(blockTransform(p0), blockTransform(p1), hs.v, true); + createLine(blockTransform(p0), blockTransform(p1), hs, /*constrainHV=*/true); } else { hRequest hr = createBulge(p0, p1, bulge); - setStyle(hr, hs); + configureRequest(hr, hs); } } } - virtual void addSpline(const DRW_Spline *data) { + void addSpline(const DRW_Spline *data) override { if(data->space != DRW::ModelSpace) return; if(data->degree != 3) return; if(addPendingBlockEntity(*data)) return; - hRequest hr = SS.GW.AddRequest(Request::CUBIC, false); + hRequest hr = SS.GW.AddRequest(Request::Type::CUBIC, /*rememberForUndo=*/false); for(int i = 0; i < 4; i++) { SK.GetEntity(hr.entity(i + 1))->PointForceTo(toVector(*data->controllist[i])); processPoint(hr.entity(i + 1)); } - setStyle(hr, styleFor(data)); + configureRequest(hr, styleFor(data)); } - virtual void addInsert(const DRW_Insert &data) { + void addInsert(const DRW_Insert &data) override { if(data.space != DRW::ModelSpace) return; if(addPendingBlockEntity(data)) return; auto bi = blocks.find(data.name); - if(bi == blocks.end()) oops(); + ssassert(bi != blocks.end(), "Inserted block does not exist"); Block *block = &bi->second; // Push transform. @@ -697,7 +755,8 @@ public: insertInsert = &data; if(data.extPoint.z == -1.0) invertXTransform(); - multBlockTransform(data.basePoint.x, data.basePoint.y, data.xscale, data.yscale, data.angle); + multBlockTransform(data.basePoint.x, data.basePoint.y, data.xscale, data.yscale, + data.angle); for(auto &e : block->entities) { addEntity(&*e); } @@ -710,7 +769,7 @@ public: blockT = t; } - virtual void addMText(const DRW_MText &data) { + void addMText(const DRW_MText &data) override { if(data.space != DRW::ModelSpace) return; if(addPendingBlockEntity(data)) return; @@ -719,14 +778,14 @@ public: addText(text); } - virtual void addText(const DRW_Text &data) { + void addText(const DRW_Text &data) override { if(data.space != DRW::ModelSpace) return; if(addPendingBlockEntity(data)) return; Constraint c = {}; c.group = SS.GW.activeGroup; c.workplane = SS.GW.ActiveWorkplane(); - c.type = Constraint::COMMENT; + c.type = Constraint::Type::COMMENT; if(data.alignH == DRW_Text::HLeft && data.alignV == DRW_Text::VBaseLine) { c.disp.offset = toVector(data.basePoint); } else { @@ -734,10 +793,10 @@ public: } c.comment = data.text; c.disp.style = styleFor(&data); - Constraint::AddConstraint(&c, false); + Constraint::AddConstraint(&c, /*rememberForUndo=*/false); } - virtual void addDimAlign(const DRW_DimAligned *data) { + void addDimAlign(const DRW_DimAligned *data) override { if(data->space != DRW::ModelSpace) return; if(addPendingBlockEntity(*data)) return; @@ -745,7 +804,7 @@ public: Vector p1 = toVector(data->getDef2Point()); Vector p2 = toVector(data->getTextPoint()); hConstraint hc = Constraint::Constrain( - Constraint::PT_PT_DISTANCE, + Constraint::Type::PT_PT_DISTANCE, createOrGetPoint(p0), createOrGetPoint(p1), Entity::NO_ENTITY @@ -760,13 +819,13 @@ public: c->disp.offset = p2.Minus(p0.Plus(p1).ScaledBy(0.5)); } - virtual void addDimLinear(const DRW_DimLinear *data) { + void addDimLinear(const DRW_DimLinear *data) override { if(data->space != DRW::ModelSpace) return; if(addPendingBlockEntity(*data)) return; - Vector p0 = toVector(data->getDef1Point(), false); - Vector p1 = toVector(data->getDef2Point(), false); - Vector p2 = toVector(data->getTextPoint(), false); + Vector p0 = toVector(data->getDef1Point(), /*transform=*/false); + Vector p1 = toVector(data->getDef2Point(), /*transform=*/false); + Vector p2 = toVector(data->getTextPoint(), /*transform=*/false); double angle = data->getAngle() * PI / 180.0; Vector dir = Vector::From(cos(angle), sin(angle), 0.0); @@ -784,10 +843,10 @@ public: p4 = blockTransform(p4); hConstraint hc = Constraint::Constrain( - Constraint::PT_LINE_DISTANCE, + Constraint::Type::PT_LINE_DISTANCE, createOrGetPoint(p0), Entity::NO_ENTITY, - createLine(p1, p3, invisibleStyle().v) + createLine(p1, p3, invisibleStyle()) ); Constraint *c = SK.GetConstraint(hc); @@ -799,7 +858,7 @@ public: c->disp.offset = p2.Minus(p4); } - virtual void addDimAngular(const DRW_DimAngular *data) { + void addDimAngular(const DRW_DimAngular *data) override { if(data->space != DRW::ModelSpace) return; if(addPendingBlockEntity(*data)) return; @@ -809,11 +868,11 @@ public: Vector l1p1 = toVector(data->getSecondLine2()); hConstraint hc = Constraint::Constrain( - Constraint::ANGLE, + Constraint::Type::ANGLE, Entity::NO_ENTITY, Entity::NO_ENTITY, - createLine(l0p0, l0p1, invisibleStyle().v), - createLine(l1p1, l1p0, invisibleStyle().v), + createLine(l0p0, l0p1, invisibleStyle()), + createLine(l1p1, l1p0, invisibleStyle()), /*other=*/false, /*other2=*/false ); @@ -835,11 +894,12 @@ public: } } - hConstraint createDiametric(Vector cp, double r, Vector tp, double actual, bool asRadius = false) { - hEntity he = createCircle(cp, r, invisibleStyle().v); + hConstraint createDiametric(Vector cp, Quaternion q, double r, Vector tp, + double actual, bool asRadius = false) { + hEntity he = createCircle(cp, q, r, invisibleStyle()); hConstraint hc = Constraint::Constrain( - Constraint::DIAMETER, + Constraint::Type::DIAMETER, Entity::NO_ENTITY, Entity::NO_ENTITY, he @@ -856,7 +916,7 @@ public: return hc; } - virtual void addDimRadial(const DRW_DimRadial *data) { + void addDimRadial(const DRW_DimRadial *data) override { if(data->space != DRW::ModelSpace) return; if(addPendingBlockEntity(*data)) return; @@ -868,10 +928,12 @@ public: actual = data->getActualMeasurement(); } - createDiametric(cp, cp.Minus(dp).Magnitude(), tp, actual, /*asRadius=*/true); + Vector nz = toVector(data->getExtrusion()); + Quaternion q = NormalFromExtPoint(nz); + createDiametric(cp, q, cp.Minus(dp).Magnitude(), tp, actual, /*asRadius=*/true); } - virtual void addDimDiametric(const DRW_DimDiametric *data) { + void addDimDiametric(const DRW_DimDiametric *data) override { if(data->space != DRW::ModelSpace) return; if(addPendingBlockEntity(*data)) return; @@ -885,10 +947,12 @@ public: actual = data->getActualMeasurement(); } - createDiametric(cp, cp.Minus(dp1).Magnitude(), tp, actual, /*asRadius=*/false); + Vector nz = toVector(data->getExtrusion()); + Quaternion q = NormalFromExtPoint(nz); + createDiametric(cp, q, cp.Minus(dp1).Magnitude(), tp, actual, /*asRadius=*/false); } - virtual void addDimAngular3P(const DRW_DimAngular3p *data) { + void addDimAngular3P(const DRW_DimAngular3p *data) override { if(data->space != DRW::ModelSpace) return; if(addPendingBlockEntity(*data)) return; @@ -901,48 +965,235 @@ public: } }; -void ImportDxf(const std::string &filename) { - DxfReadInterface interface; - interface.clearBlockTransform(); +class DxfCheck3D : public DRW_Interface { +public: + bool is3d; - std::string data; - if(!ReadFile(filename, &data)) { - Error("Couldn't read from '%s'", filename.c_str()); - return; + void addEntity(DRW_Entity *e) { + switch(e->eType) { + case DRW::POINT: + addPoint(*static_cast(e)); + break; + case DRW::LINE: + addLine(*static_cast(e)); + break; + case DRW::ARC: + addArc(*static_cast(e)); + break; + case DRW::CIRCLE: + addCircle(*static_cast(e)); + break; + case DRW::POLYLINE: + addPolyline(*static_cast(e)); + break; + case DRW::LWPOLYLINE: + addLWPolyline(*static_cast(e)); + break; + case DRW::SPLINE: + addSpline(static_cast(e)); + break; + case DRW::INSERT: + addInsert(*static_cast(e)); + break; + case DRW::TEXT: + addText(*static_cast(e)); + break; + case DRW::MTEXT: + addMText(*static_cast(e)); + break; + case DRW::DIMALIGNED: + addDimAlign(static_cast(e)); + break; + case DRW::DIMLINEAR: + addDimLinear(static_cast(e)); + break; + case DRW::DIMRADIAL: + addDimRadial(static_cast(e)); + break; + case DRW::DIMDIAMETRIC: + addDimDiametric(static_cast(e)); + break; + case DRW::DIMANGULAR: + addDimAngular(static_cast(e)); + break; + default: + break; + } } - SS.UndoRemember(); - std::stringstream stream(data); - if(!dxfRW().read(stream, &interface, /*ext=*/false)) { - Error("Corrupted DXF file."); + void addPoint(const DRW_Point &data) override { + if(data.space != DRW::ModelSpace) return; + checkCoord(data.basePoint); } - if(interface.unknownEntities > 0) { - Message(ssprintf("%u DXF entities of unknown type were ignored.", - interface.unknownEntities).c_str()); + void addLine(const DRW_Line &data) override { + if(data.space != DRW::ModelSpace) return; + checkCoord(data.basePoint); + checkCoord(data.secPoint); + } + + void addArc(const DRW_Arc &data) override { + if(data.space != DRW::ModelSpace) return; + checkCoord(data.basePoint); + checkExt(data.extPoint); + } + + void addCircle(const DRW_Circle &data) override { + if(data.space != DRW::ModelSpace) return; + checkCoord(data.basePoint); + checkExt(data.extPoint); + } + + void addPolyline(const DRW_Polyline &data) override { + if(data.space != DRW::ModelSpace) return; + for(size_t i = 0; i < data.vertlist.size(); i++) { + checkCoord(data.vertlist[i]->basePoint); + } + } + + void addSpline(const DRW_Spline *data) override { + if(data->space != DRW::ModelSpace) return; + if(data->degree != 3) return; + for(int i = 0; i < 4; i++) { + checkCoord(*data->controllist[i]); + } + } + + void addInsert(const DRW_Insert &data) override { + if(data.space != DRW::ModelSpace) return; + checkCoord(data.basePoint); + } + + void addMText(const DRW_MText &data) override { + if(data.space != DRW::ModelSpace) return; + + DRW_MText text = data; + text.secPoint = text.basePoint; + addText(text); + } + + void addText(const DRW_Text &data) override { + if(data.space != DRW::ModelSpace) return; + checkCoord(data.basePoint); + checkCoord(data.secPoint); + } + + void addDimAlign(const DRW_DimAligned *data) override { + if(data->space != DRW::ModelSpace) return; + checkCoord(data->getDef1Point()); + checkCoord(data->getDef2Point()); + checkCoord(data->getTextPoint()); } -} -void ImportDwg(const std::string &filename) { - DxfReadInterface interface; - interface.clearBlockTransform(); + void addDimLinear(const DRW_DimLinear *data) override { + if(data->space != DRW::ModelSpace) return; + checkCoord(data->getDef1Point()); + checkCoord(data->getDef2Point()); + checkCoord(data->getTextPoint()); + } + + void addDimAngular(const DRW_DimAngular *data) override { + if(data->space != DRW::ModelSpace) return; + checkCoord(data->getFirstLine1()); + checkCoord(data->getFirstLine2()); + checkCoord(data->getSecondLine1()); + checkCoord(data->getSecondLine2()); + checkCoord(data->getTextPoint()); + } + + void addDimRadial(const DRW_DimRadial *data) override { + if(data->space != DRW::ModelSpace) return; + checkCoord(data->getCenterPoint()); + checkCoord(data->getDiameterPoint()); + checkCoord(data->getTextPoint()); + checkExt(data->getExtrusion()); + } + + void addDimDiametric(const DRW_DimDiametric *data) override { + if(data->space != DRW::ModelSpace) return; + checkCoord(data->getDiameter1Point()); + checkCoord(data->getDiameter2Point()); + checkCoord(data->getTextPoint()); + checkExt(data->getExtrusion()); + } + + void addDimAngular3P(const DRW_DimAngular3p *data) override { + if(data->space != DRW::ModelSpace) return; + DRW_DimAngular dim = *static_cast(data); + + dim.setFirstLine1(data->getVertexPoint()); + dim.setFirstLine2(data->getFirstLine()); + dim.setSecondLine1(data->getVertexPoint()); + dim.setSecondLine2(data->getSecondLine()); + addDimAngular(&dim); + } + + void checkCoord(const DRW_Coord &coord) { + if(fabs(coord.z) > LENGTH_EPS) { + is3d = true; + } + } + + void checkExt(const DRW_Coord &coord) { + if ((fabs(coord.x) > 1/64.) || (fabs(coord.y) > 1/64.)) { + is3d = true; + } + } +}; + +static void +ImportDwgDxf(const Platform::Path &filename, + const std::function &read) { + std::string fileType = ToUpper(filename.Extension()); std::string data; if(!ReadFile(filename, &data)) { - Error("Couldn't read from '%s'", filename.c_str()); + Error("Couldn't read from '%s'", filename.raw.c_str()); return; } - SS.UndoRemember(); - std::stringstream stream(data); - if(!dwgR().read(stream, &interface, /*ext=*/false)) { - Error("Corrupted DWG file."); + bool asConstruction = true; + if(SS.GW.LockedInWorkplane()) { + DxfCheck3D checker = {}; + read(data, &checker); + if(checker.is3d) { + Message("This %s file contains entities with non-zero Z coordinate; " + "the entire file will be imported as construction entities in 3d.", + fileType.c_str()); + SS.GW.SetWorkplaneFreeIn3d(); + SS.GW.EnsureValidActives(); + } else { + asConstruction = false; + } } - if(interface.unknownEntities > 0) { - Message(ssprintf("%u DWG entities of unknown type were ignored.", - interface.unknownEntities).c_str()); + SS.UndoRemember(); + + DxfImport importer = {}; + importer.asConstruction = asConstruction; + importer.clearBlockTransform(); + if(!read(data, &importer)) { + Error("Corrupted %s file.", fileType.c_str()); + return; } + if(importer.unknownEntities > 0) { + Message("%u %s entities of unknown type were ignored.", + importer.unknownEntities, fileType.c_str()); + } +} + +void ImportDxf(const Platform::Path &filename) { + ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) { + std::stringstream stream(data); + return dxfRW().read(stream, intf, /*ext=*/true); + }); +} + +void ImportDwg(const Platform::Path &filename) { + ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) { + std::stringstream stream(data); + return dwgR().read(stream, intf, /*ext=*/true); + }); } } diff --git a/src/importidf.cpp b/src/importidf.cpp new file mode 100644 index 0000000..1789e5c --- /dev/null +++ b/src/importidf.cpp @@ -0,0 +1,521 @@ +//----------------------------------------------------------------------------- +// Intermediate Data Format (IDF) file reader. Reads an IDF file for PCB outlines and creates +// an equivalent SovleSpace sketch/extrusion. Supports only Linking, not import. +// Part placement is not currently supported. +// +// Copyright 2020 Paul Kahler. +//----------------------------------------------------------------------------- +#include "solvespace.h" +#include "sketch.h" + +// Split a string into substrings separated by spaces. +// Allow quotes to enclose spaces within a string +static std::vector splitString(const std::string line) { + std::vector v = {}; + + if(line.length() == 0) return v; + + std::string s = ""; + bool inString = false; + bool inQuotes = false; + + for (size_t i=0; i 0) + v.push_back(s); + + return v; +} + +////////////////////////////////////////////////////////////////////////////// +// Functions for linking an IDF file - we need to create entites that +// get remapped into a linked group similar to linking .slvs files +////////////////////////////////////////////////////////////////////////////// + +// Make a new point - type doesn't matter since we will make a copy later +static hEntity newPoint(EntityList *el, int *id, Vector p, bool visible = true) { + Entity en = {}; + en.type = Entity::Type::POINT_N_COPY; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 462; + en.actPoint = p; + en.construction = false; + en.style.v = Style::DATUM; + en.actVisible = visible; + en.forceHidden = false; + + *id = *id+1; + en.h.v = *id + en.group.v*65536; + el->Add(&en); + return en.h; +} + +static hEntity newLine(EntityList *el, int *id, hEntity p0, hEntity p1) { + Entity en = {}; + en.type = Entity::Type::LINE_SEGMENT; + en.point[0] = p0; + en.point[1] = p1; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 493; + en.construction = false; + en.style.v = Style::ACTIVE_GRP; + en.actVisible = true; + en.forceHidden = false; + + *id = *id+1; + en.h.v = *id + en.group.v*65536; + el->Add(&en); + return en.h; +} + +static hEntity newNormal(EntityList *el, int *id, Quaternion normal) { + // normals have parameters, but we don't need them to make a NORMAL_N_COPY from this + Entity en = {}; + en.type = Entity::Type::NORMAL_N_COPY; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 472; + en.actNormal = normal; + en.construction = false; + en.style.v = Style::ACTIVE_GRP; + // to be visible we need to add a point. + en.point[0] = newPoint(el, id, Vector::From(0,0,3), /*visible=*/ true); + en.actVisible = true; + en.forceHidden = false; + + *id = *id+1; + en.h.v = *id + en.group.v*65536; + el->Add(&en); + return en.h; +} + +static hEntity newArc(EntityList *el, int *id, hEntity p0, hEntity p1, hEntity pc, hEntity hnorm) { + Entity en = {}; + en.type = Entity::Type::ARC_OF_CIRCLE; + en.point[0] = pc; + en.point[1] = p0; + en.point[2] = p1; + en.normal = hnorm; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 403; + en.construction = false; + en.style.v = Style::ACTIVE_GRP; + en.actVisible = true; + en.forceHidden = false; *id = *id+1; + + *id = *id + 1; + en.h.v = *id + en.group.v*65536; + el->Add(&en); + return en.h; +} + +static hEntity newDistance(EntityList *el, int *id, double distance) { + // normals have parameters, but we don't need them to make a NORMAL_N_COPY from this + Entity en = {}; + en.type = Entity::Type::DISTANCE; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 472; + en.actDistance = distance; + en.construction = false; + en.style.v = Style::ACTIVE_GRP; + // to be visible we'll need to add a point? + en.actVisible = false; + en.forceHidden = false; + + *id = *id+1; + en.h.v = *id + en.group.v*65536; + el->Add(&en); + return en.h; +} + +static hEntity newCircle(EntityList *el, int *id, hEntity p0, hEntity hdist, hEntity hnorm) { + Entity en = {}; + en.type = Entity::Type::CIRCLE; + en.point[0] = p0; + en.normal = hnorm; + en.distance = hdist; + en.extraPoints = 0; + en.timesApplied = 0; + en.group.v = 399; + en.construction = false; + en.style.v = Style::ACTIVE_GRP; + en.actVisible = true; + en.forceHidden = false; + + *id = *id+1; + en.h.v = *id + en.group.v*65536; + el->Add(&en); + return en.h; +} + +static Vector ArcCenter(Vector p0, Vector p1, double angle) { + // locate the center of an arc + Vector m = p0.Plus(p1).ScaledBy(0.5); + Vector perp = Vector::From(p1.y-p0.y, p0.x-p1.x, 0.0).WithMagnitude(1.0); + double dist = 0; + if (angle != 180) { + dist = (p1.Minus(m).Magnitude())/tan(0.5*angle*3.141592653589793/180.0); + } else { + dist = 0.0; + } + Vector c = m.Minus(perp.ScaledBy(dist)); + return c; +} + +// Add an IDF line or arc to the entity list. According to spec, zero angle indicates a line. +// Positive angles are counter clockwise, negative are clockwise. An angle of 360 +// indicates a circle centered at x1,y1 passing through x2,y2 and is a complete loop. +static void CreateEntity(EntityList *el, int *id, hEntity h0, hEntity h1, hEntity hnorm, + Vector p0, Vector p1, double angle) { + if (angle == 0.0) { + //line + if(p0.Equals(p1)) return; + + newLine(el, id, h0, h1); + + } else if(angle == 360.0) { + // circle + double d = p1.Minus(p0).Magnitude(); + hEntity hd = newDistance(el, id, d); + newCircle(el, id, h1, hd, hnorm); + + } else { + // arc + if(angle < 0.0) { + swap(p0,p1); + swap(h0,h1); + } + // locate the center of the arc + Vector m = p0.Plus(p1).ScaledBy(0.5); + Vector perp = Vector::From(p1.y-p0.y, p0.x-p1.x, 0.0).WithMagnitude(1.0); + double dist = 0; + if (angle != 180) { + dist = (p1.Minus(m).Magnitude())/tan(0.5*angle*3.141592653589793/180.0); + } else { + dist = 0.0; + } + Vector c = m.Minus(perp.ScaledBy(dist)); + hEntity hc = newPoint(el, id, c, /*visible=*/false); + newArc(el, id, h0, h1, hc, hnorm); + } +} + +// borrowed from Entity::GenerateBezierCurves because we don't have parameters. +static void MakeBeziersForArcs(SBezierList *sbl, Vector center, Vector pa, Vector pb, + Quaternion q, double angle) { + + Vector u = q.RotationU(), v = q.RotationV(); + double r = pa.Minus(center).Magnitude(); + double theta, dtheta; + + if(angle == 360.0) { + theta = 0; + } else { + Point2d c2 = center.Project2d(u, v); + Point2d pa2 = (pa.Project2d(u, v)).Minus(c2); + + theta = atan2(pa2.y, pa2.x); + } + dtheta = angle * PI/180; + + int i, n; + if(dtheta > (3*PI/2 + 0.01)) { + n = 4; + } else if(dtheta > (PI + 0.01)) { + n = 3; + } else if(dtheta > (PI/2 + 0.01)) { + n = 2; + } else { + n = 1; + } + dtheta /= n; + + for(i = 0; i < n; i++) { + double s, c; + + c = cos(theta); + s = sin(theta); + // The start point of the curve, and the tangent vector at + // that start point. + Vector p0 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)), + t0 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c)); + + theta += dtheta; + + c = cos(theta); + s = sin(theta); + Vector p2 = center.Plus(u.ScaledBy( r*c)).Plus(v.ScaledBy(r*s)), + t2 = u.ScaledBy(-r*s). Plus(v.ScaledBy(r*c)); + + // The control point must lie on both tangents. + Vector p1 = Vector::AtIntersectionOfLines(p0, p0.Plus(t0), + p2, p2.Plus(t2), + NULL); + + SBezier sb = SBezier::From(p0, p1, p2); + sb.weight[1] = cos(dtheta/2); + sbl->l.Add(&sb); + } +} + +namespace SolveSpace { + +// Here we read the important section of an IDF file. SolveSpace Entities are directly created by +// the funcions above, which is only OK because of the way linking works. For example points do +// not have handles for solver parameters (coordinates), they only have their actPoint values +// set (or actNormal or actDistance). These are incompete entites and would be a problem if +// they were part of the sketch, but they are not. After making a list of them here, a new group +// gets created from copies of these. Those copies are complete and part of the sketch group. +bool LinkIDF(const Platform::Path &filename, EntityList *el, SMesh *m, SShell *sh) { + dbp("\nLink IDF board outline."); + el->Clear(); + std::string data; + if(!ReadFile(filename, &data)) { + Error("Couldn't read from '%s'", filename.raw.c_str()); + return false; + } + + enum IDF_SECTION { + none, + header, + board_outline, + other_outline, + routing_outline, + placement_outline, + routing_keepout, + via_keepout, + placement_group, + drilled_holes, + notes, + component_placement + } section; + + section = IDF_SECTION::none; + int record_number = 0; + int curve = -1; + int entityCount = 0; + + hEntity hprev; + hEntity hprevTop; + Vector pprev = Vector::From(0,0,0); + Vector pprevTop = Vector::From(0,0,0); + + double board_thickness = 10.0; + double scale = 1.0; //mm + bool topEntities, bottomEntities; + + Quaternion normal = Quaternion::From(Vector::From(1,0,0), Vector::From(0,1,0)); + hEntity hnorm = newNormal(el, &entityCount, normal); + + // to create the extursion we will need to collect a set of bezier curves defined + // by the perimeter, cutouts, and holes. + SBezierList sbl = {}; + + std::stringstream stream(data); + for(std::string line; getline( stream, line ); ) { + if (line.find(".END_") == 0) { + section = none; + curve = -1; + } + switch (section) { + case none: + if(line.find(".HEADER") == 0) { + section = header; + record_number = 1; + } else if (line.find(".BOARD_OUTLINE") == 0) { + section = board_outline; + record_number = 1; +// no keepouts for now - they should also be shown as construction? +// } else if (line.find(".ROUTE_KEEPOUT") == 0) { +// section = routing_keepout; +// record_number = 1; + } else if(line.find(".DRILLED_HOLES") == 0) { + section = drilled_holes; + record_number = 1; + } + break; + + case header: + if(record_number == 3) { + if(line.find("MM") != std::string::npos) { + dbp("IDF units are MM"); + scale = 1.0; + } else if(line.find("THOU") != std::string::npos) { + dbp("IDF units are thousandths of an inch"); + scale = 0.0254; + } else { + dbp("IDF import, no units found in file."); + } + } + break; + + case routing_keepout: + case board_outline: + if (record_number == 2) { + if(section == board_outline) { + topEntities = true; + bottomEntities = true; + board_thickness = std::stod(line) * scale; + dbp("IDF board thickness: %lf", board_thickness); + } else if (section == routing_keepout) { + topEntities = false; + bottomEntities = false; + if(line.find("TOP") == 0 || line.find("BOTH") == 0) + topEntities = true; + if(line.find("BOTTOM") == 0 || line.find("BOTH") == 0) + bottomEntities = true; + } + } else { // records 3+ are lines, arcs, and circles + std::vector values = splitString(line); + if(values.size() != 4) continue; + int c = stoi(values[0]); + double x = stof(values[1]); + double y = stof(values[2]); + double ang = stof(values[3]); + Vector point = Vector::From(x,y,0.0); + Vector pTop = Vector::From(x,y,board_thickness); + if(c != curve) { // start a new curve + curve = c; + if (bottomEntities) + hprev = newPoint(el, &entityCount, point, /*visible=*/false); + if (topEntities) + hprevTop = newPoint(el, &entityCount, pTop, /*visible=*/false); + pprev = point; + pprevTop = pTop; + } else { + if(section == board_outline) { + // create a bezier for the extrusion + if (ang == 0) { + // straight lines + SBezier sb = SBezier::From(pprev, point); + sbl.l.Add(&sb); + } else if (ang != 360.0) { + // Arcs + Vector c = ArcCenter(pprev, point, ang); + MakeBeziersForArcs(&sbl, c, pprev, point, normal, ang); + } else { + // circles + MakeBeziersForArcs(&sbl, point, pprev, pprev, normal, ang); + } + } + // next create the entities + // only curves and points at circle centers will be visible + bool vis = (ang == 360.0); + if (bottomEntities) { + hEntity hp = newPoint(el, &entityCount, point, /*visible=*/vis); + CreateEntity(el, &entityCount, hprev, hp, hnorm, pprev, point, ang); + pprev = point; + hprev = hp; + } + if (topEntities) { + hEntity hp = newPoint(el, &entityCount, pTop, /*visible=*/vis); + CreateEntity(el, &entityCount, hprevTop, hp, hnorm, pprevTop, pTop, ang); + pprevTop = pTop; + hprevTop = hp; + } + } + } + break; + + case other_outline: + case routing_outline: + case placement_outline: + case via_keepout: + case placement_group: + break; + + case drilled_holes: { + std::vector values = splitString(line); + if(values.size() < 6) continue; + double d = stof(values[0]); + double x = stof(values[1]); + double y = stof(values[2]); + // Only show holes likely to be useful in MCAD to reduce complexity. + if((d > 1.7) || (values[5].compare(0,3,"PIN") == 0) + || (values[5].compare(0,3,"MTG") == 0)) { + // create the entity + Vector cent = Vector::From(x,y,0.0); + hEntity hcent = newPoint(el, &entityCount, cent); + hEntity hdist = newDistance(el, &entityCount, d/2); + newCircle(el, &entityCount, hcent, hdist, hnorm); + // and again for the top + Vector cTop = Vector::From(x,y,board_thickness); + hcent = newPoint(el, &entityCount, cTop); + hdist = newDistance(el, &entityCount, d/2); + newCircle(el, &entityCount, hcent, hdist, hnorm); + // create the curves for the extrusion + Vector pt = Vector::From(x+d/2, y, 0.0); + MakeBeziersForArcs(&sbl, cent, pt, pt, normal, 360.0); + } + + break; + } + case notes: + case component_placement: + break; + + default: + section = none; + break; + } + record_number++; + } + // now we can create an extrusion from all the Bezier curves. We can skip things + // like checking for a coplanar sketch because everything is at z=0. + SPolygon polyLoops = {}; + bool allClosed; + bool allCoplanar; + Vector errorPointAt = Vector::From(0,0,0); + SEdge errorAt = {}; + + SBezierLoopSetSet sblss = {}; + sblss.FindOuterFacesFrom(&sbl, &polyLoops, NULL, + 100.0, &allClosed, &errorAt, + &allCoplanar, &errorPointAt, NULL); + + //hack for when there is no sketch yet and the first group is a linked IDF + double ctc = SS.chordTolCalculated; + if(ctc == 0.0) SS.chordTolCalculated = 0.1; //mm + // there should only by one sbls in the sblss unless a board has disjointed parts... + sh->MakeFromExtrusionOf(sblss.l.First(), Vector::From(0.0, 0.0, 0.0), + Vector::From(0.0, 0.0, board_thickness), + RgbaColor::From(0, 180, 0) ); + SS.chordTolCalculated = ctc; + sblss.Clear(); + sbl.Clear(); + sh->booleanFailed = false; + + return true; +} + +} diff --git a/src/lib.cpp b/src/lib.cpp index d87a4f0..b0d8e1d 100644 --- a/src/lib.cpp +++ b/src/lib.cpp @@ -11,26 +11,13 @@ Sketch SolveSpace::SK = {}; static System SYS; -static int IsInit = 0; - -void Group::GenerateEquations(IdList *) { - // Nothing to do for now. -} - -void SolveSpace::CnfFreezeInt(uint32_t, const std::string &) -{ - abort(); -} - -uint32_t SolveSpace::CnfThawInt(uint32_t, const std::string &) -{ +void SolveSpace::Platform::FatalError(const std::string &message) { + fprintf(stderr, "%s", message.c_str()); abort(); - return 0; } -void SolveSpace::DoMessageBox(const char *, int, int, bool) -{ - abort(); +void Group::GenerateEquations(IdList *) { + // Nothing to do for now. } extern "C" { @@ -80,11 +67,6 @@ void Slvs_MakeQuaternion(double ux, double uy, double uz, void Slvs_Solve(Slvs_System *ssys, Slvs_hGroup shg) { - if(!IsInit) { - InitHeaps(); - IsInit = 1; - } - int i; for(i = 0; i < ssys->params; i++) { Slvs_Param *sp = &(ssys->param[i]); @@ -103,16 +85,16 @@ void Slvs_Solve(Slvs_System *ssys, Slvs_hGroup shg) EntityBase e = {}; switch(se->type) { -case SLVS_E_POINT_IN_3D: e.type = Entity::POINT_IN_3D; break; -case SLVS_E_POINT_IN_2D: e.type = Entity::POINT_IN_2D; break; -case SLVS_E_NORMAL_IN_3D: e.type = Entity::NORMAL_IN_3D; break; -case SLVS_E_NORMAL_IN_2D: e.type = Entity::NORMAL_IN_2D; break; -case SLVS_E_DISTANCE: e.type = Entity::DISTANCE; break; -case SLVS_E_WORKPLANE: e.type = Entity::WORKPLANE; break; -case SLVS_E_LINE_SEGMENT: e.type = Entity::LINE_SEGMENT; break; -case SLVS_E_CUBIC: e.type = Entity::CUBIC; break; -case SLVS_E_CIRCLE: e.type = Entity::CIRCLE; break; -case SLVS_E_ARC_OF_CIRCLE: e.type = Entity::ARC_OF_CIRCLE; break; +case SLVS_E_POINT_IN_3D: e.type = Entity::Type::POINT_IN_3D; break; +case SLVS_E_POINT_IN_2D: e.type = Entity::Type::POINT_IN_2D; break; +case SLVS_E_NORMAL_IN_3D: e.type = Entity::Type::NORMAL_IN_3D; break; +case SLVS_E_NORMAL_IN_2D: e.type = Entity::Type::NORMAL_IN_2D; break; +case SLVS_E_DISTANCE: e.type = Entity::Type::DISTANCE; break; +case SLVS_E_WORKPLANE: e.type = Entity::Type::WORKPLANE; break; +case SLVS_E_LINE_SEGMENT: e.type = Entity::Type::LINE_SEGMENT; break; +case SLVS_E_CUBIC: e.type = Entity::Type::CUBIC; break; +case SLVS_E_CIRCLE: e.type = Entity::Type::CIRCLE; break; +case SLVS_E_ARC_OF_CIRCLE: e.type = Entity::Type::ARC_OF_CIRCLE; break; default: dbp("bad entity type %d", se->type); return; } @@ -132,47 +114,47 @@ default: dbp("bad entity type %d", se->type); return; SK.entity.Add(&e); } - + IdList params = {}; for(i = 0; i < ssys->constraints; i++) { Slvs_Constraint *sc = &(ssys->constraint[i]); ConstraintBase c = {}; - int t; + Constraint::Type t; switch(sc->type) { -case SLVS_C_POINTS_COINCIDENT: t = Constraint::POINTS_COINCIDENT; break; -case SLVS_C_PT_PT_DISTANCE: t = Constraint::PT_PT_DISTANCE; break; -case SLVS_C_PT_PLANE_DISTANCE: t = Constraint::PT_PLANE_DISTANCE; break; -case SLVS_C_PT_LINE_DISTANCE: t = Constraint::PT_LINE_DISTANCE; break; -case SLVS_C_PT_FACE_DISTANCE: t = Constraint::PT_FACE_DISTANCE; break; -case SLVS_C_PT_IN_PLANE: t = Constraint::PT_IN_PLANE; break; -case SLVS_C_PT_ON_LINE: t = Constraint::PT_ON_LINE; break; -case SLVS_C_PT_ON_FACE: t = Constraint::PT_ON_FACE; break; -case SLVS_C_EQUAL_LENGTH_LINES: t = Constraint::EQUAL_LENGTH_LINES; break; -case SLVS_C_LENGTH_RATIO: t = Constraint::LENGTH_RATIO; break; -case SLVS_C_EQ_LEN_PT_LINE_D: t = Constraint::EQ_LEN_PT_LINE_D; break; -case SLVS_C_EQ_PT_LN_DISTANCES: t = Constraint::EQ_PT_LN_DISTANCES; break; -case SLVS_C_EQUAL_ANGLE: t = Constraint::EQUAL_ANGLE; break; -case SLVS_C_EQUAL_LINE_ARC_LEN: t = Constraint::EQUAL_LINE_ARC_LEN; break; -case SLVS_C_LENGTH_DIFFERENCE: t = Constraint::LENGTH_DIFFERENCE; break; -case SLVS_C_SYMMETRIC: t = Constraint::SYMMETRIC; break; -case SLVS_C_SYMMETRIC_HORIZ: t = Constraint::SYMMETRIC_HORIZ; break; -case SLVS_C_SYMMETRIC_VERT: t = Constraint::SYMMETRIC_VERT; break; -case SLVS_C_SYMMETRIC_LINE: t = Constraint::SYMMETRIC_LINE; break; -case SLVS_C_AT_MIDPOINT: t = Constraint::AT_MIDPOINT; break; -case SLVS_C_HORIZONTAL: t = Constraint::HORIZONTAL; break; -case SLVS_C_VERTICAL: t = Constraint::VERTICAL; break; -case SLVS_C_DIAMETER: t = Constraint::DIAMETER; break; -case SLVS_C_PT_ON_CIRCLE: t = Constraint::PT_ON_CIRCLE; break; -case SLVS_C_SAME_ORIENTATION: t = Constraint::SAME_ORIENTATION; break; -case SLVS_C_ANGLE: t = Constraint::ANGLE; break; -case SLVS_C_PARALLEL: t = Constraint::PARALLEL; break; -case SLVS_C_PERPENDICULAR: t = Constraint::PERPENDICULAR; break; -case SLVS_C_ARC_LINE_TANGENT: t = Constraint::ARC_LINE_TANGENT; break; -case SLVS_C_CUBIC_LINE_TANGENT: t = Constraint::CUBIC_LINE_TANGENT; break; -case SLVS_C_EQUAL_RADIUS: t = Constraint::EQUAL_RADIUS; break; -case SLVS_C_PROJ_PT_DISTANCE: t = Constraint::PROJ_PT_DISTANCE; break; -case SLVS_C_WHERE_DRAGGED: t = Constraint::WHERE_DRAGGED; break; -case SLVS_C_CURVE_CURVE_TANGENT:t = Constraint::CURVE_CURVE_TANGENT; break; +case SLVS_C_POINTS_COINCIDENT: t = Constraint::Type::POINTS_COINCIDENT; break; +case SLVS_C_PT_PT_DISTANCE: t = Constraint::Type::PT_PT_DISTANCE; break; +case SLVS_C_PT_PLANE_DISTANCE: t = Constraint::Type::PT_PLANE_DISTANCE; break; +case SLVS_C_PT_LINE_DISTANCE: t = Constraint::Type::PT_LINE_DISTANCE; break; +case SLVS_C_PT_FACE_DISTANCE: t = Constraint::Type::PT_FACE_DISTANCE; break; +case SLVS_C_PT_IN_PLANE: t = Constraint::Type::PT_IN_PLANE; break; +case SLVS_C_PT_ON_LINE: t = Constraint::Type::PT_ON_LINE; break; +case SLVS_C_PT_ON_FACE: t = Constraint::Type::PT_ON_FACE; break; +case SLVS_C_EQUAL_LENGTH_LINES: t = Constraint::Type::EQUAL_LENGTH_LINES; break; +case SLVS_C_LENGTH_RATIO: t = Constraint::Type::LENGTH_RATIO; break; +case SLVS_C_EQ_LEN_PT_LINE_D: t = Constraint::Type::EQ_LEN_PT_LINE_D; break; +case SLVS_C_EQ_PT_LN_DISTANCES: t = Constraint::Type::EQ_PT_LN_DISTANCES; break; +case SLVS_C_EQUAL_ANGLE: t = Constraint::Type::EQUAL_ANGLE; break; +case SLVS_C_EQUAL_LINE_ARC_LEN: t = Constraint::Type::EQUAL_LINE_ARC_LEN; break; +case SLVS_C_LENGTH_DIFFERENCE: t = Constraint::Type::LENGTH_DIFFERENCE; break; +case SLVS_C_SYMMETRIC: t = Constraint::Type::SYMMETRIC; break; +case SLVS_C_SYMMETRIC_HORIZ: t = Constraint::Type::SYMMETRIC_HORIZ; break; +case SLVS_C_SYMMETRIC_VERT: t = Constraint::Type::SYMMETRIC_VERT; break; +case SLVS_C_SYMMETRIC_LINE: t = Constraint::Type::SYMMETRIC_LINE; break; +case SLVS_C_AT_MIDPOINT: t = Constraint::Type::AT_MIDPOINT; break; +case SLVS_C_HORIZONTAL: t = Constraint::Type::HORIZONTAL; break; +case SLVS_C_VERTICAL: t = Constraint::Type::VERTICAL; break; +case SLVS_C_DIAMETER: t = Constraint::Type::DIAMETER; break; +case SLVS_C_PT_ON_CIRCLE: t = Constraint::Type::PT_ON_CIRCLE; break; +case SLVS_C_SAME_ORIENTATION: t = Constraint::Type::SAME_ORIENTATION; break; +case SLVS_C_ANGLE: t = Constraint::Type::ANGLE; break; +case SLVS_C_PARALLEL: t = Constraint::Type::PARALLEL; break; +case SLVS_C_PERPENDICULAR: t = Constraint::Type::PERPENDICULAR; break; +case SLVS_C_ARC_LINE_TANGENT: t = Constraint::Type::ARC_LINE_TANGENT; break; +case SLVS_C_CUBIC_LINE_TANGENT: t = Constraint::Type::CUBIC_LINE_TANGENT; break; +case SLVS_C_EQUAL_RADIUS: t = Constraint::Type::EQUAL_RADIUS; break; +case SLVS_C_PROJ_PT_DISTANCE: t = Constraint::Type::PROJ_PT_DISTANCE; break; +case SLVS_C_WHERE_DRAGGED: t = Constraint::Type::WHERE_DRAGGED; break; +case SLVS_C_CURVE_CURVE_TANGENT:t = Constraint::Type::CURVE_CURVE_TANGENT; break; default: dbp("bad constraint type %d", sc->type); return; } @@ -192,6 +174,17 @@ default: dbp("bad constraint type %d", sc->type); return; c.other = (sc->other) ? true : false; c.other2 = (sc->other2) ? true : false; + c.Generate(¶ms); + if(!params.IsEmpty()) { + for(Param &p : params) { + p.h = SK.param.AddAndAssignId(&p); + c.valP = p.h; + SYS.param.Add(&p); + } + params.Clear(); + c.ModifyToSatisfy(); + } + SK.constraint.Add(&c); } @@ -209,27 +202,25 @@ default: dbp("bad constraint type %d", sc->type); return; // Now we're finally ready to solve! bool andFindBad = ssys->calculateFaileds ? true : false; - int how = SYS.Solve(&g, &(ssys->dof), &bad, andFindBad, false); + SolveResult how = SYS.Solve(&g, NULL, &(ssys->dof), &bad, andFindBad, /*andFindFree=*/false); switch(how) { - case System::SOLVED_OKAY: + case SolveResult::OKAY: ssys->result = SLVS_RESULT_OKAY; break; - case System::DIDNT_CONVERGE: + case SolveResult::DIDNT_CONVERGE: ssys->result = SLVS_RESULT_DIDNT_CONVERGE; break; - case System::REDUNDANT_DIDNT_CONVERGE: - case System::REDUNDANT_OKAY: + case SolveResult::REDUNDANT_DIDNT_CONVERGE: + case SolveResult::REDUNDANT_OKAY: ssys->result = SLVS_RESULT_INCONSISTENT; break; - case System::TOO_MANY_UNKNOWNS: + case SolveResult::TOO_MANY_UNKNOWNS: ssys->result = SLVS_RESULT_TOO_MANY_UNKNOWNS; break; - - default: oops(); } // Write the new parameter values back to our caller. @@ -242,7 +233,7 @@ default: dbp("bad constraint type %d", sc->type); return; if(ssys->failed) { // Copy over any the list of problematic constraints. for(i = 0; i < ssys->faileds && i < bad.n; i++) { - ssys->failed[i] = bad.elem[i].v; + ssys->failed[i] = bad[i].v; } ssys->faileds = bad.n; } diff --git a/src/mesh.cpp b/src/mesh.cpp index 6458e5d..57377d0 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -8,7 +8,7 @@ #include -void SMesh::Clear(void) { +void SMesh::Clear() { l.Clear(); } @@ -33,13 +33,11 @@ void SMesh::AddTriangle(STriMeta meta, Vector a, Vector b, Vector c) { t.c = c; AddTriangle(&t); } -void SMesh::AddTriangle(STriangle *st) { - RgbaColor color = st->meta.color; - if(color.ToARGB32() != 0 && color.alpha != 255) isTransparent = true; +void SMesh::AddTriangle(const STriangle *st) { l.Add(st); } -void SMesh::DoBounding(Vector v, Vector *vmax, Vector *vmin) { +void SMesh::DoBounding(Vector v, Vector *vmax, Vector *vmin) const { vmax->x = max(vmax->x, v.x); vmax->y = max(vmax->y, v.y); vmax->z = max(vmax->z, v.z); @@ -48,12 +46,12 @@ void SMesh::DoBounding(Vector v, Vector *vmax, Vector *vmin) { vmin->y = min(vmin->y, v.y); vmin->z = min(vmin->z, v.z); } -void SMesh::GetBounding(Vector *vmax, Vector *vmin) { +void SMesh::GetBounding(Vector *vmax, Vector *vmin) const { int i; *vmin = Vector::From( 1e12, 1e12, 1e12); *vmax = Vector::From(-1e12, -1e12, -1e12); for(i = 0; i < l.n; i++) { - STriangle *st = &(l.elem[i]); + const STriangle *st = &(l[i]); DoBounding(st->a, vmax, vmin); DoBounding(st->b, vmax, vmin); DoBounding(st->c, vmax, vmin); @@ -72,7 +70,7 @@ void SMesh::MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d) { m.l.ClearTags(); int i; for(i = 0; i < m.l.n; i++) { - STriangle *tr = &(m.l.elem[i]); + STriangle *tr = &(m.l[i]); if((fabs(n.Dot(tr->a) - d) >= LENGTH_EPS) || (fabs(n.Dot(tr->b) - d) >= LENGTH_EPS) || @@ -86,20 +84,19 @@ void SMesh::MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d) { // Select the naked edges in our resulting open mesh. SKdNode *root = SKdNode::From(&m); root->SnapToMesh(&m); - root->MakeCertainEdgesInto(sel, SKdNode::NAKED_OR_SELF_INTER_EDGES, - false, NULL, NULL); + root->MakeCertainEdgesInto(sel, EdgeKind::NAKED_OR_SELF_INTER, + /*coplanarIsInter=*/false, NULL, NULL); m.Clear(); } -void SMesh::MakeCertainEdgesAndOutlinesInto(SEdgeList *sel, SOutlineList *sol, int type) { +void SMesh::MakeOutlinesInto(SOutlineList *sol, EdgeKind edgeKind) { SKdNode *root = SKdNode::From(this); - root->MakeCertainEdgesInto(sel, type, false, NULL, NULL); - root->MakeOutlinesInto(sol); + root->MakeOutlinesInto(sol, edgeKind); } //----------------------------------------------------------------------------- -// When we are called, all of the triangles from l.elem[start] to the end must +// When we are called, all of the triangles from l[start] to the end must // be coplanar. So we try to find a set of fewer triangles that covers the // exact same area, in order to reduce the number of triangles in the mesh. // We use this after a triangle has been split against the BSP. @@ -111,20 +108,20 @@ void SMesh::MakeCertainEdgesAndOutlinesInto(SEdgeList *sel, SOutlineList *sol, i void SMesh::Simplify(int start) { int maxTriangles = (l.n - start) + 10; - STriMeta meta = l.elem[start].meta; + STriMeta meta = l[start].meta; - STriangle *tout = (STriangle *)AllocTemporary(maxTriangles*sizeof(*tout)); + STriangle *tout = new STriangle[maxTriangles]; int toutc = 0; Vector n = Vector::From(0, 0, 0); - Vector *conv = (Vector *)AllocTemporary(maxTriangles*3*sizeof(*conv)); + Vector *conv = new Vector[maxTriangles * 3]; int convc = 0; int start0 = start; int i, j; for(i = start; i < l.n; i++) { - STriangle *tr = &(l.elem[i]); + STriangle *tr = &(l[i]); if(tr->MinAltitude() < LENGTH_EPS) { tr->tag = 1; } else { @@ -136,7 +133,7 @@ void SMesh::Simplify(int start) { bool didAdd; convc = 0; for(i = start; i < l.n; i++) { - STriangle *tr = &(l.elem[i]); + STriangle *tr = &(l[i]); if(tr->tag) continue; tr->tag = 1; @@ -161,7 +158,7 @@ void SMesh::Simplify(int start) { Vector c; for(i = start; i < l.n; i++) { - STriangle *tr = &(l.elem[i]); + STriangle *tr = &(l[i]); if(tr->tag) continue; if((tr->a).Equals(d) && (tr->b).Equals(b)) { @@ -189,8 +186,7 @@ void SMesh::Simplify(int start) { if(fabs(bDot) < LENGTH_EPS && fabs(dDot) < LENGTH_EPS) { conv[WRAP((j+1), convc)] = c; // and remove the vertex at j, which is a dup - memmove(conv+j, conv+j+1, - (convc - j - 1)*sizeof(conv[0])); + std::move(conv+j+1, conv+convc, conv+j); convc--; } else if(fabs(bDot) < LENGTH_EPS && dDot > 0) { conv[j] = c; @@ -198,8 +194,7 @@ void SMesh::Simplify(int start) { conv[WRAP((j+1), convc)] = c; } else if(bDot > 0 && dDot > 0) { // conv[j] is unchanged, conv[j+1] goes to [j+2] - memmove(conv+j+2, conv+j+1, - (convc - j - 1)*sizeof(conv[0])); + std::move_backward(conv+j+1, conv+convc, conv+convc+1); conv[j+1] = c; convc++; } else { @@ -239,15 +234,15 @@ void SMesh::Simplify(int start) { for(i = 0; i < toutc; i++) { AddTriangle(&(tout[i])); } - FreeTemporary(tout); - FreeTemporary(conv); + delete[] tout; + delete[] conv; } void SMesh::AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3) { int i; for(i = 0; i < srcm->l.n; i++) { - STriangle *st = &(srcm->l.elem[i]); + STriangle *st = &(srcm->l[i]); int pn = l.n; atLeastOneDiscarded = false; SBsp3::InsertOrCreate(bsp3, st, this); @@ -270,11 +265,12 @@ void SMesh::MakeFromUnionOf(SMesh *a, SMesh *b) { SBsp3 *bspb = SBsp3::FromMesh(b); flipNormal = false; - keepCoplanar = false; - AddAgainstBsp(b, bspa); + keepInsideOtherShell = false; - flipNormal = false; keepCoplanar = true; + AddAgainstBsp(b, bspa); + + keepCoplanar = false; AddAgainstBsp(a, bspb); } @@ -284,17 +280,33 @@ void SMesh::MakeFromDifferenceOf(SMesh *a, SMesh *b) { flipNormal = true; keepCoplanar = true; + keepInsideOtherShell = true; AddAgainstBsp(b, bspa); flipNormal = false; + keepCoplanar = false; + keepInsideOtherShell = false; + AddAgainstBsp(a, bspb); +} + +void SMesh::MakeFromIntersectionOf(SMesh *a, SMesh *b) { + SBsp3 *bspa = SBsp3::FromMesh(a); + SBsp3 *bspb = SBsp3::FromMesh(b); + + keepInsideOtherShell = true; + flipNormal = false; + keepCoplanar = false; AddAgainstBsp(a, bspb); + + keepCoplanar = true; + AddAgainstBsp(b, bspa); } void SMesh::MakeFromCopyOf(SMesh *a) { - int i; - for(i = 0; i < a->l.n; i++) { - AddTriangle(&(a->l.elem[i])); + ssassert(this != a, "Can't make from copy of self"); + for(int i = 0; i < a->l.n; i++) { + AddTriangle(&(a->l[i])); } } @@ -303,8 +315,8 @@ void SMesh::MakeFromAssemblyOf(SMesh *a, SMesh *b) { MakeFromCopyOf(b); } -void SMesh::MakeFromTransformationOf(SMesh *a, - Vector trans, Quaternion q, double scale) +void SMesh::MakeFromTransformationOf(SMesh *a, Vector trans, + Quaternion q, double scale) { STriangle *tr; for(tr = a->l.First(); tr; tr = a->l.NextAfter(tr)) { @@ -323,43 +335,44 @@ void SMesh::MakeFromTransformationOf(SMesh *a, } } -bool SMesh::IsEmpty(void) { - return (l.n == 0); -} +bool SMesh::IsEmpty() const { return (l.IsEmpty()); } -uint32_t SMesh::FirstIntersectionWith(Point2d mp) { - Vector p0 = Vector::From(mp.x, mp.y, 0); - Vector gn = Vector::From(0, 0, 1); +uint32_t SMesh::FirstIntersectionWith(Point2d mp) const { + Vector rayPoint = SS.GW.UnProjectPoint3(Vector::From(mp.x, mp.y, 0.0)); + Vector rayDir = SS.GW.UnProjectPoint3(Vector::From(mp.x, mp.y, 1.0)).Minus(rayPoint); - double maxT = -1e12; uint32_t face = 0; - - int i; - for(i = 0; i < l.n; i++) { - STriangle tr = l.elem[i]; - tr.a = SS.GW.ProjectPoint3(tr.a); - tr.b = SS.GW.ProjectPoint3(tr.b); - tr.c = SS.GW.ProjectPoint3(tr.c); - - Vector n = tr.Normal(); - - if(n.Dot(gn) < LENGTH_EPS) continue; // back-facing or on edge - - if(tr.ContainsPointProjd(gn, p0)) { - // Let our line have the form r(t) = p0 + gn*t - double t = -(n.Dot((tr.a).Minus(p0)))/(n.Dot(gn)); - if(t > maxT) { - maxT = t; - face = tr.meta.face; - } + double faceT = VERY_NEGATIVE; + for(int i = 0; i < l.n; i++) { + const STriangle &tr = l[i]; + if(tr.meta.face == 0) continue; + + double t; + if(!tr.Raytrace(rayPoint, rayDir, &t, NULL)) continue; + if(t > faceT) { + face = tr.meta.face; + faceT = t; } } + return face; } -STriangleLl *STriangleLl::Alloc(void) +Vector SMesh::GetCenterOfMass() const { + Vector center = {}; + double vol = 0.0; + for(int i = 0; i < l.n; i++) { + const STriangle &tr = l[i]; + double tvol = tr.SignedVolume(); + center = center.Plus(tr.a.Plus(tr.b.Plus(tr.c)).ScaledBy(tvol / 4.0)); + vol += tvol; + } + return center.ScaledBy(1.0 / vol); +} + +STriangleLl *STriangleLl::Alloc() { return (STriangleLl *)AllocTemporary(sizeof(STriangleLl)); } -SKdNode *SKdNode::Alloc(void) +SKdNode *SKdNode::Alloc() { return (SKdNode *)AllocTemporary(sizeof(SKdNode)); } SKdNode *SKdNode::From(SMesh *m) { @@ -367,7 +380,7 @@ SKdNode *SKdNode::From(SMesh *m) { STriangle *tra = (STriangle *)AllocTemporary((m->l.n) * sizeof(*tra)); for(i = 0; i < m->l.n; i++) { - tra[i] = m->l.elem[i]; + tra[i] = m->l[i]; } srand(0); @@ -488,7 +501,7 @@ leaf: return ret; } -void SKdNode::ClearTags(void) { +void SKdNode::ClearTags() const { if(gt && lt) { gt->ClearTags(); lt->ClearTags(); @@ -525,7 +538,7 @@ void SKdNode::AddTriangle(STriangle *tr) { } } -void SKdNode::MakeMeshInto(SMesh *m) { +void SKdNode::MakeMeshInto(SMesh *m) const { if(gt) gt->MakeMeshInto(m); if(lt) lt->MakeMeshInto(m); @@ -538,7 +551,7 @@ void SKdNode::MakeMeshInto(SMesh *m) { } } -void SKdNode::ListTrianglesInto(std::vector *tl) { +void SKdNode::ListTrianglesInto(std::vector *tl) const { if(gt) gt->ListTrianglesInto(tl); if(lt) lt->ListTrianglesInto(tl); @@ -580,16 +593,20 @@ void SKdNode::SnapToVertex(Vector v, SMesh *extras) { bool mightHit = true; for(k = 0; k < 3; k++) { - if((tr->a).Element(k) < v.Element(k) - KDTREE_EPS && - (tr->b).Element(k) < v.Element(k) - KDTREE_EPS && - (tr->c).Element(k) < v.Element(k) - KDTREE_EPS) + double trA = (tr->a).Element(k); + double trB = (tr->b).Element(k); + double trC = (tr->c).Element(k); + double vk = v.Element(k); + if(trA < vk - KDTREE_EPS && + trB < vk - KDTREE_EPS && + trC < vk - KDTREE_EPS) { mightHit = false; break; } - if((tr->a).Element(k) > v.Element(k) + KDTREE_EPS && - (tr->b).Element(k) > v.Element(k) + KDTREE_EPS && - (tr->c).Element(k) > v.Element(k) + KDTREE_EPS) + if(trA > vk + KDTREE_EPS && + trB > vk + KDTREE_EPS && + trC > vk + KDTREE_EPS) { mightHit = false; break; @@ -601,6 +618,10 @@ void SKdNode::SnapToVertex(Vector v, SMesh *extras) { if(tr->b.Equals(v)) { tr->b = v; continue; } if(tr->c.Equals(v)) { tr->c = v; continue; } + if(tr->IsDegenerate()) { + continue; + } + if(v.OnLineSegment(tr->a, tr->b)) { STriangle nt = STriangle::From(tr->meta, tr->a, v, tr->c); extras->AddTriangle(&nt); @@ -631,18 +652,19 @@ void SKdNode::SnapToVertex(Vector v, SMesh *extras) { void SKdNode::SnapToMesh(SMesh *m) { int i, j, k; for(i = 0; i < m->l.n; i++) { - STriangle *tr = &(m->l.elem[i]); + STriangle *tr = &(m->l[i]); + if(tr->IsDegenerate()) { + continue; + } for(j = 0; j < 3; j++) { - Vector v = ((j == 0) ? tr->a : - ((j == 1) ? tr->b : - tr->c)); + Vector v = tr->vertices[j]; SMesh extra = {}; SnapToVertex(v, &extra); for(k = 0; k < extra.l.n; k++) { STriangle *tra = (STriangle *)AllocTemporary(sizeof(*tra)); - *tra = extra.l.elem[k]; + *tra = extra.l[k]; AddTriangle(tra); } extra.Clear(); @@ -652,10 +674,10 @@ void SKdNode::SnapToMesh(SMesh *m) { //----------------------------------------------------------------------------- // For all the edges in sel, split them against the given triangle, and test -// them for occlusion. Keep only the visible segments. sel is both our input -// and our output. +// them for occlusion. sel is both our input and our output. tag indicates +// whether an edge is occluded. //----------------------------------------------------------------------------- -void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool removeHidden) { +void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) const { SEdgeList seln = {}; Vector tn = tr->Normal().WithMagnitude(1); @@ -665,7 +687,8 @@ void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool remo if(tn.z > LENGTH_EPS) { // If the edge crosses our triangle's plane, then split into above // and below parts. Note that we must preserve auxA, which contains - // the style associated with this line. + // the style associated with this line, as well as the tag, which + // contains the occlusion status. SEdge *se; for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { double da = (se->a).Dot(tn) - td, @@ -676,12 +699,12 @@ void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool remo Vector m = Vector::AtIntersectionOfPlaneAndLine( tn, td, se->a, se->b, NULL); - seln.AddEdge(m, se->b, se->auxA); + seln.AddEdge(m, se->b, se->auxA, 0, se->tag); se->b = m; } } for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) { - sel->AddEdge(se->a, se->b, se->auxA); + sel->AddEdge(se->a, se->b, se->auxA, 0, se->tag); } seln.Clear(); @@ -727,48 +750,49 @@ void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool remo double dab = (db - da); Vector spl = ((se->a).ScaledBy( db/dab)).Plus( (se->b).ScaledBy(-da/dab)); - seln.AddEdge(spl, se->b, se->auxA); + seln.AddEdge(spl, se->b, se->auxA, 0, se->tag); se->b = spl; } } for(se = seln.l.First(); se; se = seln.l.NextAfter(se)) { // The split pieces are all behind the triangle, since only // edges behind the triangle got split. So their auxB is 0. - sel->AddEdge(se->a, se->b, se->auxA, 0); + sel->AddEdge(se->a, se->b, se->auxA, 0, se->tag); } seln.Clear(); } for(se = sel->l.First(); se; se = sel->l.NextAfter(se)) { + bool occluded; if(se->auxB) { // Lies above or on the triangle plane, so triangle doesn't // occlude it. - se->tag = 0; + occluded = false; } else { // Test the segment to see if it lies outside the triangle // (i.e., outside wrt at least one edge), and keep it only // then. Point2d pt = ((se->a).Plus(se->b).ScaledBy(0.5)).ProjectXy(); - se->tag = 1; + occluded = true; for(i = 0; i < 3; i++) { // If the test point lies on the boundary of our triangle, // then we still discard the edge. - if(n[i].Dot(pt) - d[i] > LENGTH_EPS) se->tag = 0; + if(n[i].Dot(pt) - d[i] > LENGTH_EPS) occluded = false; } } - if(!removeHidden && se->tag == 1) - se->auxA = Style::HIDDEN_EDGE; + + if(occluded) { + se->tag = 1; + } } - if(removeHidden) - sel->l.RemoveTagged(); } } //----------------------------------------------------------------------------- // Given an edge orig, occlusion test it against our mesh. We output an edge -// list in sel, containing the visible portions of that edge. +// list in sel, where only invisible portions of the edge are tagged. //----------------------------------------------------------------------------- -void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool removeHidden) { +void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) const { if(gt && lt) { double ac = (orig.a).Element(which), bc = (orig.b).Element(which); @@ -778,13 +802,13 @@ void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool remove bc < c + KDTREE_EPS || which == 2) { - lt->OcclusionTestLine(orig, sel, cnt, removeHidden); + lt->OcclusionTestLine(orig, sel, cnt); } if(ac > c - KDTREE_EPS || bc > c - KDTREE_EPS || which == 2) { - gt->OcclusionTestLine(orig, sel, cnt, removeHidden); + gt->OcclusionTestLine(orig, sel, cnt); } } else { STriangleLl *ll; @@ -793,7 +817,7 @@ void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool remove if(tr->tag == cnt) continue; - SplitLinesAgainstTriangle(sel, tr, removeHidden); + SplitLinesAgainstTriangle(sel, tr); tr->tag = cnt; } } @@ -809,7 +833,7 @@ void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool remove // with a triangle in the mesh, otherwise not. //----------------------------------------------------------------------------- void SKdNode::FindEdgeOn(Vector a, Vector b, int cnt, bool coplanarIsInter, - EdgeOnInfo *info) + EdgeOnInfo *info) const { if(gt && lt) { double ac = a.Element(which), @@ -849,7 +873,7 @@ void SKdNode::FindEdgeOn(Vector a, Vector b, int cnt, bool coplanarIsInter, } // Record the triangle info->tr = tr; - // And record which vertexes a and b correspond to + // And record which vertices a and b correspond to info->ai = a.Equals(tr->a) ? 0 : (a.Equals(tr->b) ? 1 : 2); info->bi = b.Equals(tr->a) ? 0 : (b.Equals(tr->b) ? 1 : 2); } else if(((a.Equals(tr->a) && b.Equals(tr->b)) || @@ -925,8 +949,8 @@ static bool CheckAndAddTrianglePair(std::set // * emphasized edges (i.e., edges where a triangle from one face joins // a triangle from a different face) //----------------------------------------------------------------------------- -void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter, - bool *inter, bool *leaky, int auxA) +void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, EdgeKind how, bool coplanarIsInter, + bool *inter, bool *leaky, int auxA) const { if(inter) *inter = false; if(leaky) *leaky = false; @@ -939,17 +963,23 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter int cnt = 1234; for(STriangle *tr : tris) { for(int j = 0; j < 3; j++) { - Vector a = (j == 0) ? tr->a : ((j == 1) ? tr->b : tr->c); - Vector b = (j == 0) ? tr->b : ((j == 1) ? tr->c : tr->a); + Vector a = tr->vertices[j]; + Vector b = tr->vertices[(j + 1) % 3]; SKdNode::EdgeOnInfo info = {}; FindEdgeOn(a, b, cnt, coplanarIsInter, &info); switch(how) { - case NAKED_OR_SELF_INTER_EDGES: + case EdgeKind::NAKED_OR_SELF_INTER: + // there should be one anti-parllel edge if(info.count != 1) { - sel->AddEdge(a, b, auxA); - if(leaky) *leaky = true; + // but there may be multiple parallel coincident edges + SKdNode::EdgeOnInfo parallelInfo = {}; + FindEdgeOn(b, a, -cnt, coplanarIsInter, ¶llelInfo); + if (info.count != parallelInfo.count) { + sel->AddEdge(a, b, auxA); + if(leaky) *leaky = true; + } } if(info.intersectsMesh) { sel->AddEdge(a, b, auxA); @@ -957,14 +987,14 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter } break; - case SELF_INTER_EDGES: + case EdgeKind::SELF_INTER: if(info.intersectsMesh) { sel->AddEdge(a, b, auxA); if(inter) *inter = true; } break; - case TURNING_EDGES: + case EdgeKind::TURNING: if((tr->Normal().z < LENGTH_EPS) && (info.count == 1) && info.frontFacing) @@ -978,7 +1008,7 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter } break; - case EMPHASIZED_EDGES: + case EdgeKind::EMPHASIZED: if(info.count == 1 && tr->meta.face != info.tr->meta.face) { if(CheckAndAddTrianglePair(&edgeTris, tr, info.tr)) break; @@ -990,20 +1020,12 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter } break; - case SHARP_EDGES: + case EdgeKind::SHARP: if(info.count == 1) { - Vector na0 = (j == 0) ? tr->an : - ((j == 1) ? tr->bn : tr->cn); - Vector nb0 = (j == 0) ? tr->bn : - ((j == 1) ? tr->cn : tr->an); - Vector na1 = (info.ai == 0) ? info.tr->an : - ((info.ai == 1) ? info.tr->bn : info.tr->cn); - Vector nb1 = (info.bi == 0) ? info.tr->an : - ((info.bi == 1) ? info.tr->bn : info.tr->cn); - na0 = na0.WithMagnitude(1.0); - nb0 = nb0.WithMagnitude(1.0); - na1 = na1.WithMagnitude(1.0); - nb1 = nb1.WithMagnitude(1.0); + Vector na0 = tr->normals[j].WithMagnitude(1.0); + Vector nb0 = tr->normals[(j + 1) % 3].WithMagnitude(1.0); + Vector na1 = info.tr->normals[info.ai].WithMagnitude(1.0); + Vector nb1 = info.tr->normals[info.bi].WithMagnitude(1.0); if(!((na0.Equals(na1) && nb0.Equals(nb1)) || (na0.Equals(nb1) && nb0.Equals(na1)))) { if(CheckAndAddTrianglePair(&edgeTris, tr, info.tr)) @@ -1014,8 +1036,6 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter } } break; - - default: oops(); } cnt++; @@ -1023,7 +1043,7 @@ void SKdNode::MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter } } -void SKdNode::MakeOutlinesInto(SOutlineList *sol) +void SKdNode::MakeOutlinesInto(SOutlineList *sol, EdgeKind edgeKind) const { std::vector tris; ClearTags(); @@ -1033,8 +1053,8 @@ void SKdNode::MakeOutlinesInto(SOutlineList *sol) int cnt = 1234; for(STriangle *tr : tris) { for(int j = 0; j < 3; j++) { - Vector a = (j == 0) ? tr->a : ((j == 1) ? tr->b : tr->c); - Vector b = (j == 0) ? tr->b : ((j == 1) ? tr->c : tr->a); + Vector a = tr->vertices[j]; + Vector b = tr->vertices[(j + 1) % 3]; SKdNode::EdgeOnInfo info = {}; FindEdgeOn(a, b, cnt, /*coplanarIsInter=*/false, &info); @@ -1043,27 +1063,66 @@ void SKdNode::MakeOutlinesInto(SOutlineList *sol) if(CheckAndAddTrianglePair(&edgeTris, tr, info.tr)) continue; - sol->AddEdge(a, b, tr->Normal(), info.tr->Normal()); + int tag = 0; + switch(edgeKind) { + case EdgeKind::EMPHASIZED: + if(tr->meta.face != info.tr->meta.face) { + tag = 1; + } + break; + + case EdgeKind::SHARP: { + Vector na0 = tr->normals[j].WithMagnitude(1.0); + Vector nb0 = tr->normals[(j + 1) % 3].WithMagnitude(1.0); + Vector na1 = info.tr->normals[info.ai].WithMagnitude(1.0); + Vector nb1 = info.tr->normals[info.bi].WithMagnitude(1.0); + if(!((na0.Equals(na1) && nb0.Equals(nb1)) || + (na0.Equals(nb1) && nb0.Equals(na1)))) { + tag = 1; + } + } + break; + + default: + ssassert(false, "Unexpected edge kind"); + } + + Vector nl = tr->Normal().WithMagnitude(1.0); + Vector nr = info.tr->Normal().WithMagnitude(1.0); + + // We don't add edges with the same left and right + // normals because they can't produce outlines. + if(tag == 0 && nl.Equals(nr)) continue; + sol->AddEdge(a, b, nl, nr, tag); } } } +bool SOutline::IsVisible(Vector projDir) const { + double ldot = nl.Dot(projDir); + double rdot = nr.Dot(projDir); + return (ldot > -LENGTH_EPS) == (rdot < LENGTH_EPS) || + (rdot > -LENGTH_EPS) == (ldot < LENGTH_EPS); +} + void SOutlineList::Clear() { l.Clear(); } -void SOutlineList::AddEdge(Vector a, Vector b, Vector nl, Vector nr) { +void SOutlineList::AddEdge(Vector a, Vector b, Vector nl, Vector nr, int tag) { SOutline so = {}; so.a = a; so.b = b; so.nl = nl; so.nr = nr; + so.tag = tag; l.Add(&so); } -void SOutlineList::FillOutlineTags(Vector projDir) { - for(SOutline *so = l.First(); so; so = l.NextAfter(so)) { - so->tag = ((so->nl.Dot(projDir) > 0.0) != (so->nr.Dot(projDir) > 0.0)); +void SOutlineList::ListTaggedInto(SEdgeList *el, int auxA, int auxB) { + for(const SOutline &so : l) { + if(so.tag == 0) continue; + el->AddEdge(so.a, so.b, auxA, auxB); } } @@ -1072,3 +1131,87 @@ void SOutlineList::MakeFromCopyOf(SOutlineList *sol) { l.Add(so); } } + +void SMesh::PrecomputeTransparency() { + std::sort(l.begin(), l.end(), + [&](const STriangle &sta, const STriangle &stb) { + RgbaColor colora = sta.meta.color, + colorb = stb.meta.color; + bool opaquea = colora.IsEmpty() || colora.alpha == 255, + opaqueb = colorb.IsEmpty() || colorb.alpha == 255; + + if(!opaquea || !opaqueb) isTransparent = true; + return (opaquea != opaqueb && opaquea == true); + }); +} + +void SMesh::RemoveDegenerateTriangles() { + for(auto &tr : l) { + tr.tag = (int)tr.IsDegenerate(); + } + l.RemoveTagged(); +} + +double SMesh::CalculateVolume() const { + double vol = 0; + for(STriangle tr : l) { + // Translate to place vertex A at (x, y, 0) + Vector trans = Vector::From(tr.a.x, tr.a.y, 0); + tr.a = (tr.a).Minus(trans); + tr.b = (tr.b).Minus(trans); + tr.c = (tr.c).Minus(trans); + + // Rotate to place vertex B on the y-axis. Depending on + // whether the triangle is CW or CCW, C is either to the + // right or to the left of the y-axis. This handles the + // sign of our normal. + Vector u = Vector::From(-tr.b.y, tr.b.x, 0); + u = u.WithMagnitude(1); + Vector v = Vector::From(tr.b.x, tr.b.y, 0); + v = v.WithMagnitude(1); + Vector n = Vector::From(0, 0, 1); + + tr.a = (tr.a).DotInToCsys(u, v, n); + tr.b = (tr.b).DotInToCsys(u, v, n); + tr.c = (tr.c).DotInToCsys(u, v, n); + + n = tr.Normal().WithMagnitude(1); + + // Triangles on edge don't contribute + if(fabs(n.z) < LENGTH_EPS) continue; + + // The plane has equation p dot n = a dot n + double d = (tr.a).Dot(n); + // nx*x + ny*y + nz*z = d + // nz*z = d - nx*x - ny*y + double A = -n.x/n.z, B = -n.y/n.z, C = d/n.z; + + double mac = tr.c.y/tr.c.x, mbc = (tr.c.y - tr.b.y)/tr.c.x; + double xc = tr.c.x, yb = tr.b.y; + + // I asked Maple for + // int(int(A*x + B*y +C, y=mac*x..(mbc*x + yb)), x=0..xc); + double integral = + (1.0/3)*( + A*(mbc-mac)+ + (1.0/2)*B*(mbc*mbc-mac*mac) + )*(xc*xc*xc)+ + (1.0/2)*(A*yb+B*yb*mbc+C*(mbc-mac))*xc*xc+ + C*yb*xc+ + (1.0/2)*B*yb*yb*xc; + + vol += integral; + } + return vol; +} + +double SMesh::CalculateSurfaceArea(const std::vector &faces) const { + double area = 0.0; + for(uint32_t f : faces) { + for(const STriangle &t : l) { + if(f != t.meta.face) continue; + area += t.Area(); + } + } + return area; +} diff --git a/src/modify.cpp b/src/modify.cpp index 47eee36..94b2478 100644 --- a/src/modify.cpp +++ b/src/modify.cpp @@ -12,11 +12,11 @@ // Useful when splitting, tangent arcing, or removing bezier points. //----------------------------------------------------------------------------- void GraphicsWindow::ReplacePointInConstraints(hEntity oldpt, hEntity newpt) { - int i; - for(i = 0; i < SK.constraint.n; i++) { - Constraint *c = &(SK.constraint.elem[i]); - if(c->ptA.v == oldpt.v) c->ptA = newpt; - if(c->ptB.v == oldpt.v) c->ptB = newpt; + for(auto &c : SK.constraint) { + if(c.ptA == oldpt) + c.ptA = newpt; + if(c.ptB == oldpt) + c.ptB = newpt; } } @@ -25,14 +25,13 @@ void GraphicsWindow::ReplacePointInConstraints(hEntity oldpt, hEntity newpt) { //----------------------------------------------------------------------------- void GraphicsWindow::RemoveConstraintsForPointBeingDeleted(hEntity hpt) { SK.constraint.ClearTags(); - for(int i = 0; i < SK.constraint.n; i++) { - Constraint *c = &(SK.constraint.elem[i]); - if(c->ptA.v == hpt.v || c->ptB.v == hpt.v) { - c->tag = 1; + for(auto &c : SK.constraint) { + if(c.ptA == hpt || c.ptB == hpt) { + c.tag = 1; (SS.deleted.constraints)++; - if(c->type != Constraint::POINTS_COINCIDENT && - c->type != Constraint::HORIZONTAL && - c->type != Constraint::VERTICAL) + if(c.type != Constraint::Type::POINTS_COINCIDENT && + c.type != Constraint::Type::HORIZONTAL && + c.type != Constraint::Type::VERTICAL) { (SS.deleted.nonTrivialConstraints)++; } @@ -49,15 +48,15 @@ void GraphicsWindow::RemoveConstraintsForPointBeingDeleted(hEntity hpt) { //----------------------------------------------------------------------------- void GraphicsWindow::FixConstraintsForRequestBeingDeleted(hRequest hr) { Request *r = SK.GetRequest(hr); - if(r->group.v != SS.GW.activeGroup.v) return; + if(r->group != SS.GW.activeGroup) return; Entity *e; for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { if(!(e->h.isFromRequest())) continue; - if(e->h.request().v != hr.v) continue; + if(e->h.request() != hr) continue; - if(e->type != Entity::POINT_IN_2D && - e->type != Entity::POINT_IN_3D) + if(e->type != Entity::Type::POINT_IN_2D && + e->type != Entity::Type::POINT_IN_3D) { continue; } @@ -73,14 +72,14 @@ void GraphicsWindow::FixConstraintsForPointBeingDeleted(hEntity hpt) { Constraint *c; SK.constraint.ClearTags(); for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { - if(c->type != Constraint::POINTS_COINCIDENT) continue; - if(c->group.v != SS.GW.activeGroup.v) continue; + if(c->type != Constraint::Type::POINTS_COINCIDENT) continue; + if(c->group != SS.GW.activeGroup) continue; - if(c->ptA.v == hpt.v) { + if(c->ptA == hpt) { ld.Add(&(c->ptB)); c->tag = 1; } - if(c->ptB.v == hpt.v) { + if(c->ptB == hpt) { ld.Add(&(c->ptA)); c->tag = 1; } @@ -97,9 +96,8 @@ void GraphicsWindow::FixConstraintsForPointBeingDeleted(hEntity hpt) { // those two points were implicitly coincident with each other. By // deleting hpt (and all constraints that mention it), we will delete // that relationship. So put it back here now. - int i; - for(i = 1; i < ld.n; i++) { - Constraint::ConstrainCoincident(ld.elem[i-1], ld.elem[i]); + for(int i = 1; i < ld.n; i++) { + Constraint::ConstrainCoincident(ld[i-1], ld[i]); } ld.Clear(); } @@ -111,14 +109,14 @@ void GraphicsWindow::FixConstraintsForPointBeingDeleted(hEntity hpt) { void GraphicsWindow::ParametricCurve::MakeFromEntity(hEntity he, bool reverse) { *this = {}; Entity *e = SK.GetEntity(he); - if(e->type == Entity::LINE_SEGMENT) { + if(e->type == Entity::Type::LINE_SEGMENT) { isLine = true; p0 = e->EndpointStart(), p1 = e->EndpointFinish(); if(reverse) { swap(p0, p1); } - } else if(e->type == Entity::ARC_OF_CIRCLE) { + } else if(e->type == Entity::Type::ARC_OF_CIRCLE) { isLine = false; p0 = SK.GetEntity(e->point[0])->PointGetNum(); Vector pe = SK.GetEntity(e->point[1])->PointGetNum(); @@ -131,11 +129,9 @@ void GraphicsWindow::ParametricCurve::MakeFromEntity(hEntity he, bool reverse) { EntityBase *wrkpln = SK.GetEntity(e->workplane)->Normal(); u = wrkpln->NormalU(); v = wrkpln->NormalV(); - } else { - oops(); - } + } else ssassert(false, "Unexpected entity type"); } -double GraphicsWindow::ParametricCurve::LengthForAuto(void) { +double GraphicsWindow::ParametricCurve::LengthForAuto() { if(isLine) { // Allow a third of the line to disappear with auto radius return (p1.Minus(p0)).Magnitude() / 3; @@ -163,46 +159,65 @@ Vector GraphicsWindow::ParametricCurve::TangentAt(double t) { return t; } } -hRequest GraphicsWindow::ParametricCurve::CreateRequestTrimmedTo(double t, - bool extraConstraints, hEntity orig, hEntity arc, bool arcFinish) +/** Changes or copies the given entity and connects it to the arc. + * \param t Where on this parametric curve does it connect to the arc. + * \param reuseOrig Should the original entity be modified + * \param orig The original entity. + * \param arc The arc that will be connected to. + * \param arcFinish Whether to connect to the end point of the arc. + * \param pointf When changing the original entity, whether the end point should be modified. + */ +void GraphicsWindow::ParametricCurve::CreateRequestTrimmedTo(double t, + bool reuseOrig, hEntity orig, hEntity arc, bool arcFinish, bool pointf) { hRequest hr; Entity *e; if(isLine) { - hr = SS.GW.AddRequest(Request::LINE_SEGMENT, false), - e = SK.GetEntity(hr.entity(0)); - SK.GetEntity(e->point[0])->PointForceTo(PointAt(t)); - SK.GetEntity(e->point[1])->PointForceTo(PointAt(1)); - ConstrainPointIfCoincident(e->point[0]); - ConstrainPointIfCoincident(e->point[1]); - if(extraConstraints) { - Constraint::Constrain(Constraint::PT_ON_LINE, + if (reuseOrig) { + e = SK.GetEntity(orig); + int i = pointf ? 1 : 0; + SK.GetEntity(e->point[i])->PointForceTo(PointAt(t)); + ConstrainPointIfCoincident(e->point[i]); + } else { + hr = SS.GW.AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false), + e = SK.GetEntity(hr.entity(0)); + SK.GetEntity(e->point[0])->PointForceTo(PointAt(t)); + SK.GetEntity(e->point[1])->PointForceTo(PointAt(1)); + ConstrainPointIfCoincident(e->point[0]); + ConstrainPointIfCoincident(e->point[1]); + Constraint::Constrain(Constraint::Type::PT_ON_LINE, hr.entity(1), Entity::NO_ENTITY, orig); } - Constraint::Constrain(Constraint::ARC_LINE_TANGENT, + Constraint::Constrain(Constraint::Type::ARC_LINE_TANGENT, Entity::NO_ENTITY, Entity::NO_ENTITY, - arc, e->h, arcFinish, false); + arc, e->h, /*other=*/arcFinish, /*other2=*/false); } else { - hr = SS.GW.AddRequest(Request::ARC_OF_CIRCLE, false), - e = SK.GetEntity(hr.entity(0)); - SK.GetEntity(e->point[0])->PointForceTo(p0); - if(dtheta > 0) { - SK.GetEntity(e->point[1])->PointForceTo(PointAt(t)); - SK.GetEntity(e->point[2])->PointForceTo(PointAt(1)); + if (reuseOrig) { + e = SK.GetEntity(orig); + int i = pointf ? 2 : 1; + SK.GetEntity(e->point[i])->PointForceTo(PointAt(t)); + ConstrainPointIfCoincident(e->point[i]); } else { - SK.GetEntity(e->point[2])->PointForceTo(PointAt(t)); - SK.GetEntity(e->point[1])->PointForceTo(PointAt(1)); + hr = SS.GW.AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false), + e = SK.GetEntity(hr.entity(0)); + SK.GetEntity(e->point[0])->PointForceTo(p0); + if(dtheta > 0) { + SK.GetEntity(e->point[1])->PointForceTo(PointAt(t)); + SK.GetEntity(e->point[2])->PointForceTo(PointAt(1)); + } else { + SK.GetEntity(e->point[2])->PointForceTo(PointAt(t)); + SK.GetEntity(e->point[1])->PointForceTo(PointAt(1)); + } + ConstrainPointIfCoincident(e->point[0]); + ConstrainPointIfCoincident(e->point[1]); + ConstrainPointIfCoincident(e->point[2]); } - ConstrainPointIfCoincident(e->point[0]); - ConstrainPointIfCoincident(e->point[1]); - ConstrainPointIfCoincident(e->point[2]); // The tangency constraint alone is enough to fully constrain it, // so there's no need for more. - Constraint::Constrain(Constraint::CURVE_CURVE_TANGENT, + Constraint::Constrain(Constraint::Type::CURVE_CURVE_TANGENT, Entity::NO_ENTITY, Entity::NO_ENTITY, - arc, e->h, arcFinish, (dtheta < 0)); + arc, e->h, /*other=*/arcFinish, /*other2=*/(dtheta < 0)); } - return hr; } //----------------------------------------------------------------------------- @@ -216,10 +231,10 @@ void GraphicsWindow::ParametricCurve::ConstrainPointIfCoincident(hEntity hpt) { ptv = pt->PointGetNum(); for(e = SK.entity.First(); e; e = SK.entity.NextAfter(e)) { - if(e->h.v == pt->h.v) continue; + if(e->h == pt->h) continue; if(!e->IsPoint()) continue; - if(e->group.v != pt->group.v) continue; - if(e->workplane.v != pt->workplane.v) continue; + if(e->group != pt->group) continue; + if(e->workplane != pt->workplane) continue; ev = e->PointGetNum(); if(!ev.Equals(ptv)) continue; @@ -234,10 +249,9 @@ void GraphicsWindow::ParametricCurve::ConstrainPointIfCoincident(hEntity hpt) { // non-construction line segments that join at this point, and create a // tangent arc joining them. //----------------------------------------------------------------------------- -void GraphicsWindow::MakeTangentArc(void) { +void GraphicsWindow::MakeTangentArc() { if(!LockedInWorkplane()) { - Error("Must be sketching in workplane to create tangent " - "arc."); + Error(_("Must be sketching in workplane to create tangent arc.")); return; } @@ -254,18 +268,18 @@ void GraphicsWindow::MakeTangentArc(void) { hRequest hreq[2]; hEntity hent[2]; bool pointf[2]; - for(i = 0; i < SK.request.n; i++) { - Request *r = &(SK.request.elem[i]); - if(r->group.v != activeGroup.v) continue; - if(r->workplane.v != ActiveWorkplane().v) continue; - if(r->construction) continue; - if(r->type != Request::LINE_SEGMENT && - r->type != Request::ARC_OF_CIRCLE) - { + for(auto &r : SK.request) { + if(r.group != activeGroup) + continue; + if(r.workplane != ActiveWorkplane()) + continue; + if(r.construction) + continue; + if(r.type != Request::Type::LINE_SEGMENT && r.type != Request::Type::ARC_OF_CIRCLE) { continue; } - Entity *e = SK.GetEntity(r->h.entity(0)); + Entity *e = SK.GetEntity(r.h.entity(0)); Vector ps = e->EndpointStart(), pf = e->EndpointFinish(); @@ -276,17 +290,17 @@ void GraphicsWindow::MakeTangentArc(void) { // finish of this entity. ent[c] = e; hent[c] = e->h; - req[c] = r; - hreq[c] = r->h; + req[c] = &r; + hreq[c] = r.h; pointf[c] = (pf.Equals(pshared)); } c++; } } if(c != 2) { - Error("To create a tangent arc, select a point where two " - "non-construction lines or cicles in this group and " - "workplane join."); + Error(_("To create a tangent arc, select a point where two " + "non-construction lines or circles in this group and " + "workplane join.")); return; } @@ -359,8 +373,8 @@ void GraphicsWindow::MakeTangentArc(void) { tp[1] = t[1]; // And convert those points to parameter values along the curve. - t[0] += (pa0.Minus(p0)).DivPivoting(t0); - t[1] += (pa1.Minus(p1)).DivPivoting(t1); + t[0] += (pa0.Minus(p0)).DivProjected(t0); + t[1] += (pa1.Minus(p1)).DivProjected(t1); } // Stupid check for convergence, and for an out of range result (as @@ -369,11 +383,11 @@ void GraphicsWindow::MakeTangentArc(void) { if(fabs(tp[0] - t[0]) > 1e-3 || fabs(tp[1] - t[1]) > 1e-3 || t[0] < 0.01 || t[1] < 0.01 || t[0] > 0.99 || t[1] > 0.99 || - isnan(t[0]) || isnan(t[1])) + IsReasonable(t[0]) || IsReasonable(t[1])) { - Error("Couldn't round this corner. Try a smaller radius, or try " - "creating the desired geometry by hand with tangency " - "constraints."); + Error(_("Couldn't round this corner. Try a smaller radius, or try " + "creating the desired geometry by hand with tangency " + "constraints.")); return; } @@ -391,7 +405,28 @@ void GraphicsWindow::MakeTangentArc(void) { SS.UndoRemember(); - hRequest harc = AddRequest(Request::ARC_OF_CIRCLE, false); + if (SS.tangentArcModify) { + // Delete the coincident constraint for the removed point. + SK.constraint.ClearTags(); + for(i = 0; i < SK.constraint.n; i++) { + Constraint *cs = &(SK.constraint[i]); + if(cs->group != activeGroup) continue; + if(cs->workplane != ActiveWorkplane()) continue; + if(cs->type != Constraint::Type::POINTS_COINCIDENT) continue; + if (SK.GetEntity(cs->ptA)->PointGetNum().Equals(pshared)) { + cs->tag = 1; + } + } + SK.constraint.RemoveTagged(); + } else { + // Make the original entities construction, or delete them + // entirely, according to user preference. + SK.GetRequest(hreq[0])->construction = true; + SK.GetRequest(hreq[1])->construction = true; + } + + // Create and position the new tangent arc. + hRequest harc = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false); Entity *earc = SK.GetEntity(harc.entity(0)); hEntity hearc = earc->h; @@ -401,29 +436,11 @@ void GraphicsWindow::MakeTangentArc(void) { earc = NULL; - pc[0].CreateRequestTrimmedTo(t[0], !SS.tangentArcDeleteOld, - hent[0], hearc, (b == 1)); - pc[1].CreateRequestTrimmedTo(t[1], !SS.tangentArcDeleteOld, - hent[1], hearc, (a == 1)); - - // Now either make the original entities construction, or delete them - // entirely, according to user preference. - Request *re; - SK.request.ClearTags(); - for(re = SK.request.First(); re; re = SK.request.NextAfter(re)) { - if(re->h.v == hreq[0].v || re->h.v == hreq[1].v) { - if(SS.tangentArcDeleteOld) { - re->tag = 1; - } else { - re->construction = true; - } - } - } - if(SS.tangentArcDeleteOld) { - DeleteTaggedRequests(); - } - - SS.ScheduleGenerateAll(); + // Modify or duplicate the original entities and connect them to the tangent arc. + pc[0].CreateRequestTrimmedTo(t[0], SS.tangentArcModify, + hent[0], hearc, /*arcFinish=*/(b == 1), pointf[0]); + pc[1].CreateRequestTrimmedTo(t[1], SS.tangentArcModify, + hent[1], hearc, /*arcFinish=*/(a == 1), pointf[1]); } hEntity GraphicsWindow::SplitLine(hEntity he, Vector pinter) { @@ -434,8 +451,8 @@ hEntity GraphicsWindow::SplitLine(hEntity he, Vector pinter) { p1 = SK.GetEntity(hep1)->PointGetNum(); // Add the two line segments this one gets split into. - hRequest r0i = AddRequest(Request::LINE_SEGMENT, false), - ri1 = AddRequest(Request::LINE_SEGMENT, false); + hRequest r0i = AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false), + ri1 = AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false); // Don't get entities till after adding, realloc issues Entity *e0i = SK.GetEntity(r0i.entity(0)), @@ -454,12 +471,12 @@ hEntity GraphicsWindow::SplitLine(hEntity he, Vector pinter) { hEntity GraphicsWindow::SplitCircle(hEntity he, Vector pinter) { Entity *circle = SK.GetEntity(he); - if(circle->type == Entity::CIRCLE) { + if(circle->type == Entity::Type::CIRCLE) { // Start with an unbroken circle, split it into a 360 degree arc. Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); circle = NULL; // shortly invalid! - hRequest hr = AddRequest(Request::ARC_OF_CIRCLE, false); + hRequest hr = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false); Entity *arc = SK.GetEntity(hr.entity(0)); @@ -479,8 +496,8 @@ hEntity GraphicsWindow::SplitCircle(hEntity he, Vector pinter) { finish = SK.GetEntity(hf)->PointGetNum(); circle = NULL; // shortly invalid! - hRequest hr0 = AddRequest(Request::ARC_OF_CIRCLE, false), - hr1 = AddRequest(Request::ARC_OF_CIRCLE, false); + hRequest hr0 = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false), + hr1 = AddRequest(Request::Type::ARC_OF_CIRCLE, /*rememberForUndo=*/false); Entity *arc0 = SK.GetEntity(hr0.entity(0)), *arc1 = SK.GetEntity(hr1.entity(0)); @@ -517,18 +534,18 @@ hEntity GraphicsWindow::SplitCubic(hEntity he, Vector pinter) { double t; int i, j; for(i = 0; i < sbl.l.n; i++) { - SBezier *sb = &(sbl.l.elem[i]); - if(sb->deg != 3) oops(); + SBezier *sb = &(sbl.l[i]); + ssassert(sb->deg == 3, "Expected a cubic bezier"); - sb->ClosestPointTo(pinter, &t, false); + sb->ClosestPointTo(pinter, &t, /*mustConverge=*/false); if(pinter.Equals(sb->PointAt(t))) { // Split that segment at the intersection. SBezier b0i, bi1, b01 = *sb; b01.SplitAt(t, &b0i, &bi1); // Add the two cubic segments this one gets split into. - hRequest r0i = AddRequest(Request::CUBIC, false), - ri1 = AddRequest(Request::CUBIC, false); + hRequest r0i = AddRequest(Request::Type::CUBIC, /*rememberForUndo=*/false), + ri1 = AddRequest(Request::Type::CUBIC, /*rememberForUndo=*/false); // Don't get entities till after adding, realloc issues Entity *e0i = SK.GetEntity(r0i.entity(0)), @@ -546,7 +563,7 @@ hEntity GraphicsWindow::SplitCubic(hEntity he, Vector pinter) { hep1n = ei1->point[3]; hepin = e0i->point[3]; } else { - hRequest r = AddRequest(Request::CUBIC, false); + hRequest r = AddRequest(Request::Type::CUBIC, /*rememberForUndo=*/false); Entity *e = SK.GetEntity(r.entity(0)); for(j = 0; j <= 3; j++) { @@ -567,32 +584,33 @@ hEntity GraphicsWindow::SplitCubic(hEntity he, Vector pinter) { hEntity GraphicsWindow::SplitEntity(hEntity he, Vector pinter) { Entity *e = SK.GetEntity(he); - int entityType = e->type; + Entity::Type entityType = e->type; hEntity ret; if(e->IsCircle()) { ret = SplitCircle(he, pinter); - } else if(e->type == Entity::LINE_SEGMENT) { + } else if(e->type == Entity::Type::LINE_SEGMENT) { ret = SplitLine(he, pinter); - } else if(e->type == Entity::CUBIC || e->type == Entity::CUBIC_PERIODIC) { + } else if(e->type == Entity::Type::CUBIC || e->type == Entity::Type::CUBIC_PERIODIC) { ret = SplitCubic(he, pinter); } else { - Error("Couldn't split this entity; lines, circles, or cubics only."); + Error(_("Couldn't split this entity; lines, circles, or cubics only.")); return Entity::NO_ENTITY; } // Finally, delete the request that generated the original entity. - int reqType = EntReqTable::GetRequestForEntity(entityType); + Request::Type reqType = EntReqTable::GetRequestForEntity(entityType); SK.request.ClearTags(); - for(int i = 0; i < SK.request.n; i++) { - Request *r = &(SK.request.elem[i]); - if(r->group.v != activeGroup.v) continue; - if(r->type != reqType) continue; + for(auto &r : SK.request) { + if(r.group != activeGroup) + continue; + if(r.type != reqType) + continue; // If the user wants to keep the old entities around, they can just // mark them construction first. - if(he.v == r->h.entity(0).v && !r->construction) { - r->tag = 1; + if(he == r.h.entity(0) && !r.construction) { + r.tag = 1; break; } } @@ -601,63 +619,122 @@ hEntity GraphicsWindow::SplitEntity(hEntity he, Vector pinter) { return ret; } -void GraphicsWindow::SplitLinesOrCurves(void) { +void GraphicsWindow::SplitLinesOrCurves() { if(!LockedInWorkplane()) { - Error("Must be sketching in workplane to split."); + Error(_("Must be sketching in workplane to split.")); return; } GroupSelection(); - if(!(gs.n == 2 &&(gs.lineSegments + - gs.circlesOrArcs + - gs.cubics + - gs.periodicCubics) == 2)) - { - Error("Select two entities that intersect each other (e.g. two lines " - "or two circles or a circle and a line)."); + int n = gs.lineSegments + gs.circlesOrArcs + gs.cubics + gs.periodicCubics; + if(!((n == 2 && gs.points == 0) || (n == 1 && gs.points == 1))) { + Error(_("Select two entities that intersect each other " + "(e.g. two lines/circles/arcs or a line/circle/arc and a point).")); return; } + bool splitAtPoint = (gs.points == 1); hEntity ha = gs.entity[0], - hb = gs.entity[1]; + hb = splitAtPoint ? gs.point[0] : gs.entity[1]; + Entity *ea = SK.GetEntity(ha), *eb = SK.GetEntity(hb); - - // Compute the possibly-rational Bezier curves for each of these entities - SBezierList sbla, sblb; - sbla = {}; - sblb = {}; - ea->GenerateBezierCurves(&sbla); - eb->GenerateBezierCurves(&sblb); - // and then compute the points where they intersect, based on those curves. SPointList inters = {}; - sbla.AllIntersectionsWith(&sblb, &inters); - - if(inters.l.n > 0) { - Vector pi = Vector::From(0, 0, 0); - // If there's multiple points, then take the one closest to the - // mouse pointer. - double dmin = VERY_POSITIVE; - SPoint *sp; - for(sp = inters.l.First(); sp; sp = inters.l.NextAfter(sp)) { - double d = ProjectPoint(sp->p).DistanceTo(currentMousePosition); - if(d < dmin) { - dmin = d; - pi = sp->p; + SBezierList sbla = {}, + sblb = {}; + Vector pi = Vector::From(0, 0, 0); + + SK.constraint.ClearTags(); + + // First, decide the point where we're going to make the split. + bool foundInters = false; + if(splitAtPoint) { + // One of the entities is a point, and this point must be on the other entity. + // Verify that a corresponding point-coincident constraint exists for the point/entity. + Vector p0, p1; + if(ea->type == Entity::Type::LINE_SEGMENT) { + p0 = ea->EndpointStart(); + p1 = ea->EndpointFinish(); + } + + for(Constraint &c : SK.constraint) { + if(c.ptA.request() == hb.request() && + c.entityA.request() == ha.request()) { + pi = SK.GetEntity(c.ptA)->PointGetNum(); + + if(ea->type == Entity::Type::LINE_SEGMENT && !pi.OnLineSegment(p0, p1)) { + // The point isn't between line endpoints, so there isn't an actual + // intersection. + continue; + } + + c.tag = 1; + foundInters = true; + break; + } + } + } else { + // Compute the possibly-rational Bezier curves for each of these non-point entities... + ea->GenerateBezierCurves(&sbla); + eb->GenerateBezierCurves(&sblb); + // ... and then compute the points where they intersect, based on those curves. + sbla.AllIntersectionsWith(&sblb, &inters); + + // If there's multiple points, then take the one closest to the mouse pointer. + if(!inters.l.IsEmpty()) { + double dmin = VERY_POSITIVE; + SPoint *sp; + for(sp = inters.l.First(); sp; sp = inters.l.NextAfter(sp)) { + double d = ProjectPoint(sp->p).DistanceTo(currentMousePosition); + if(d < dmin) { + dmin = d; + pi = sp->p; + } } } + foundInters = true; + } + + // Then, actually split the entities. + if(foundInters) { SS.UndoRemember(); + + // Remove any constraints we're going to replace. + SK.constraint.RemoveTagged(); + hEntity hia = SplitEntity(ha, pi), - hib = SplitEntity(hb, pi); + hib = {}; // SplitEntity adds the coincident constraints to join the split halves // of each original entity; and then we add the constraint to join // the two entities together at the split point. - if(hia.v && hib.v) { - Constraint::ConstrainCoincident(hia, hib); + if(splitAtPoint) { + // Remove datum point, as it has now been superseded by the split point. + SK.request.ClearTags(); + for(Request &r : SK.request) { + if(r.h == hb.request()) { + if(r.type == Request::Type::DATUM_POINT) { + // Delete datum point. + r.tag = 1; + FixConstraintsForRequestBeingDeleted(r.h); + } else { + // Add constraint if not datum point, but endpoint of line/arc etc. + Constraint::ConstrainCoincident(hia, hb); + } + break; + } + } + SK.request.RemoveTagged(); + } else { + // Split second non-point entity and add constraint. + hib = SplitEntity(hb, pi); + if(hia.v && hib.v) { + Constraint::ConstrainCoincident(hia, hib); + } } } else { - Error("Can't split; no intersection found."); + Error(_("Can't split; no intersection found.")); + return; } // All done, clean up and regenerate. @@ -665,6 +742,4 @@ void GraphicsWindow::SplitLinesOrCurves(void) { sbla.Clear(); sblb.Clear(); ClearSelection(); - SS.ScheduleGenerateAll(); } - diff --git a/src/mouse.cpp b/src/mouse.cpp index 700b765..699f3e2 100644 --- a/src/mouse.cpp +++ b/src/mouse.cpp @@ -24,15 +24,15 @@ void GraphicsWindow::AddPointToDraggedList(hEntity hp) { // twice as far as the mouse pointer... List *lhe = &(pending.points); for(hEntity *hee = lhe->First(); hee; hee = lhe->NextAfter(hee)) { - if(hee->v == hp.v) { + if(*hee == hp) { // Exact same point. return; } Entity *pe = SK.GetEntity(*hee); if(pe->type == p->type && - pe->type != Entity::POINT_IN_2D && - pe->type != Entity::POINT_IN_3D && - pe->group.v == p->group.v) + pe->type != Entity::Type::POINT_IN_2D && + pe->type != Entity::Type::POINT_IN_3D && + pe->group == p->group) { // Transform-type point, from the same group. So it handles the // same unknowns. @@ -46,12 +46,13 @@ void GraphicsWindow::StartDraggingByEntity(hEntity he) { Entity *e = SK.GetEntity(he); if(e->IsPoint()) { AddPointToDraggedList(e->h); - } else if(e->type == Entity::LINE_SEGMENT || - e->type == Entity::ARC_OF_CIRCLE || - e->type == Entity::CUBIC || - e->type == Entity::CUBIC_PERIODIC || - e->type == Entity::CIRCLE || - e->type == Entity::TTF_TEXT) + } else if(e->type == Entity::Type::LINE_SEGMENT || + e->type == Entity::Type::ARC_OF_CIRCLE || + e->type == Entity::Type::CUBIC || + e->type == Entity::Type::CUBIC_PERIODIC || + e->type == Entity::Type::CIRCLE || + e->type == Entity::Type::TTF_TEXT || + e->type == Entity::Type::IMAGE) { int pts; EntReqTable::GetEntityInfo(e->type, e->extraPoints, @@ -62,7 +63,7 @@ void GraphicsWindow::StartDraggingByEntity(hEntity he) { } } -void GraphicsWindow::StartDraggingBySelection(void) { +void GraphicsWindow::StartDraggingBySelection() { List *ls = &(selection); for(Selection *s = ls->First(); s; s = ls->NextAfter(s)) { if(!s->entity.v) continue; @@ -72,13 +73,18 @@ void GraphicsWindow::StartDraggingBySelection(void) { // The user might select a point, and then click it again to start // dragging; but the point just got unselected by that click. So drag // the hovered item too, and they'll always have it. - if(hover.entity.v) StartDraggingByEntity(hover.entity); + if(hover.entity.v) { + hEntity dragEntity = ChooseFromHoverToDrag().entity; + if(dragEntity != Entity::NO_ENTITY) { + StartDraggingByEntity(dragEntity); + } + } } void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown) { - if(GraphicsEditControlIsVisible()) return; + if(window->IsEditorVisible()) return; if(context.active) return; SS.extraLine.draw = false; @@ -102,11 +108,11 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, } } - if(!leftDown && (pending.operation == DRAGGING_POINTS || - pending.operation == DRAGGING_MARQUEE)) + if(!leftDown && (pending.operation == Pending::DRAGGING_POINTS || + pending.operation == Pending::DRAGGING_MARQUEE)) { ClearPending(); - InvalidateGraphics(); + Invalidate(); } Point2d mp = Point2d::From(x, y); @@ -129,8 +135,14 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, if(!(shiftDown || ctrlDown)) { double s = 0.3*(PI/180)*scale; // degrees per pixel - projRight = orig.projRight.RotatedAbout(orig.projUp, -s*dx); - projUp = orig.projUp.RotatedAbout(orig.projRight, s*dy); + if(SS.turntableNav) { // lock the Z to vertical + projRight = orig.projRight.RotatedAbout(Vector::From(0, 0, 1), -s * dx); + projUp = orig.projUp.RotatedAbout( + Vector::From(orig.projRight.x, orig.projRight.y, orig.projRight.y), s * dy); + } else { + projRight = orig.projRight.RotatedAbout(orig.projUp, -s * dx); + projUp = orig.projUp.RotatedAbout(orig.projRight, s * dy); + } NormalizeProjectionVectors(); } else if(ctrlDown) { @@ -157,34 +169,35 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, orig.mouse.x = x; orig.mouse.y = y; - if(SS.TW.shown.screen == TextWindow::SCREEN_EDIT_VIEW) { + if(SS.TW.shown.screen == TextWindow::Screen::EDIT_VIEW) { if(havePainted) { SS.ScheduleShowTW(); } } - InvalidateGraphics(); + Invalidate(); havePainted = false; return; } - if(pending.operation == 0) { + if(pending.operation == Pending::NONE) { double dm = orig.mouse.DistanceTo(mp); // If we're currently not doing anything, then see if we should // start dragging something. if(leftDown && dm > 3) { Entity *e = NULL; - if(hover.entity.v) e = SK.GetEntity(hover.entity); - if(e && e->type != Entity::WORKPLANE) { - Entity *e = SK.GetEntity(hover.entity); - if(e->type == Entity::CIRCLE && selection.n <= 1) { + hEntity dragEntity = ChooseFromHoverToDrag().entity; + if(dragEntity.v) e = SK.GetEntity(dragEntity); + if(e && e->type != Entity::Type::WORKPLANE) { + Entity *e = SK.GetEntity(dragEntity); + if(e->type == Entity::Type::CIRCLE && selection.n <= 1) { // Drag the radius. ClearSelection(); - pending.circle = hover.entity; - pending.operation = DRAGGING_RADIUS; + pending.circle = dragEntity; + pending.operation = Pending::DRAGGING_RADIUS; } else if(e->IsNormal()) { ClearSelection(); - pending.normal = hover.entity; - pending.operation = DRAGGING_NORMAL; + pending.normal = dragEntity; + pending.operation = Pending::DRAGGING_NORMAL; } else { if(!hoverWasSelectedOnMousedown) { // The user clicked an unselected entity, which @@ -202,16 +215,16 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, ClearSelection(); } hover.Clear(); - pending.operation = DRAGGING_POINTS; + pending.operation = Pending::DRAGGING_POINTS; } } else if(hover.constraint.v && SK.GetConstraint(hover.constraint)->HasLabel()) { ClearSelection(); pending.constraint = hover.constraint; - pending.operation = DRAGGING_CONSTRAINT; + pending.operation = Pending::DRAGGING_CONSTRAINT; } - if(pending.operation != 0) { + if(pending.operation != Pending::NONE) { // We just started a drag, so remember for the undo before // the drag changes anything. SS.UndoRemember(); @@ -223,10 +236,10 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, if(hover.entity.v) { // Avoid accidentally selecting workplanes when // starting drags. - MakeUnselected(hover.entity, false); + MakeUnselected(hover.entity, /*coincidentPointTrick=*/false); hover.Clear(); } - pending.operation = DRAGGING_MARQUEE; + pending.operation = Pending::DRAGGING_MARQUEE; orig.marqueePoint = UnProjectPoint(orig.mouseOnButtonDown); } @@ -252,7 +265,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, // If the user has started an operation from the menu, but not // completed it, then just do the selection. - if(pending.operation < FIRST_PENDING) { + if(pending.operation == Pending::COMMAND) { HitTestMakeSelection(mp); return; } @@ -261,7 +274,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, // painted since the last time we solved, do nothing, because there's // no sense solving a frame and not displaying it. if(!havePainted) { - if(pending.operation == DRAGGING_POINTS && ctrlDown) { + if(pending.operation == Pending::DRAGGING_POINTS && ctrlDown) { SS.extraLine.ptA = UnProjectPoint(orig.mouseOnButtonDown); SS.extraLine.ptB = UnProjectPoint(mp); SS.extraLine.draw = true; @@ -271,30 +284,31 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, havePainted = false; switch(pending.operation) { - case DRAGGING_CONSTRAINT: { + case Pending::DRAGGING_CONSTRAINT: { Constraint *c = SK.constraint.FindById(pending.constraint); UpdateDraggedNum(&(c->disp.offset), x, y); orig.mouse = mp; - InvalidateGraphics(); - break; + Invalidate(); + return; } - case DRAGGING_NEW_LINE_POINT: + case Pending::DRAGGING_NEW_LINE_POINT: if(!ctrlDown) { - SS.GW.pending.suggestion = - SS.GW.SuggestLineConstraint(SS.GW.pending.request); + SS.GW.pending.hasSuggestion = + SS.GW.SuggestLineConstraint(SS.GW.pending.request, &SS.GW.pending.suggestion); } else { - SS.GW.pending.suggestion = SUGGESTED_NONE; + SS.GW.pending.hasSuggestion = false; } - case DRAGGING_NEW_POINT: + // fallthrough + case Pending::DRAGGING_NEW_POINT: UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); SS.MarkGroupDirtyByEntity(pending.point); orig.mouse = mp; - InvalidateGraphics(); + Invalidate(); break; - case DRAGGING_POINTS: + case Pending::DRAGGING_POINTS: if(shiftDown || ctrlDown) { // Edit the rotation associated with a POINT_N_ROT_TRANS, // either within (ctrlDown) or out of (shiftDown) the plane @@ -333,7 +347,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, List *lhe = &(pending.points); for(hEntity *he = lhe->First(); he; he = lhe->NextAfter(he)) { Entity *e = SK.GetEntity(*he); - if(e->type != Entity::POINT_N_ROT_TRANS) { + if(e->type != Entity::Type::POINT_N_ROT_TRANS) { if(ctrlDown) { Vector p = e->PointGetNum(); p = p.Minus(SS.extraLine.ptA); @@ -364,12 +378,12 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, } break; - case DRAGGING_NEW_CUBIC_POINT: { + case Pending::DRAGGING_NEW_CUBIC_POINT: { UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); hRequest hr = pending.point.request(); - if(pending.point.v == hr.entity(4).v) { + if(pending.point == hr.entity(4)) { // The very first segment; dragging final point drags both // tangent points. Vector p0 = SK.GetEntity(hr.entity(1))->PointGetNum(), @@ -392,7 +406,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, SS.MarkGroupDirtyByEntity(pending.point); break; } - case DRAGGING_NEW_ARC_POINT: { + case Pending::DRAGGING_NEW_ARC_POINT: { UpdateDraggedPoint(pending.point, x, y); HitTestMakeSelection(mp); @@ -407,8 +421,8 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, SS.MarkGroupDirtyByEntity(pending.point); break; } - case DRAGGING_NEW_RADIUS: - case DRAGGING_RADIUS: { + case Pending::DRAGGING_NEW_RADIUS: + case Pending::DRAGGING_RADIUS: { Entity *circle = SK.GetEntity(pending.circle); Vector center = SK.GetEntity(circle->point[0])->PointGetNum(); Point2d c2 = ProjectPoint(center); @@ -419,7 +433,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, break; } - case DRAGGING_NORMAL: { + case Pending::DRAGGING_NORMAL: { Entity *normal = SK.GetEntity(pending.normal); Vector p = SK.GetEntity(normal->point[0])->PointGetNum(); Point2d p2 = ProjectPoint(p); @@ -450,45 +464,47 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, break; } - case DRAGGING_MARQUEE: + case Pending::DRAGGING_MARQUEE: orig.mouse = mp; - InvalidateGraphics(); - break; - - default: oops(); - } + Invalidate(); + return; - if(pending.operation != 0 && - pending.operation != DRAGGING_CONSTRAINT && - pending.operation != DRAGGING_MARQUEE) - { - SS.GenerateAll(); - - // Activate degraded mode, and regenerate display items without edges. - if(activeGroup.v != 0) { - bool showEdges = SS.GW.showEdges; - SS.GW.showEdges = false; - SK.GetGroup(activeGroup)->GenerateDisplayItems(); - SS.GW.showEdges = showEdges; - isDegraded = true; - } + case Pending::NONE: + case Pending::COMMAND: + ssassert(false, "Unexpected pending operation"); } } -void GraphicsWindow::ClearPending(void) { +void GraphicsWindow::ClearPending(bool scheduleShowTW) { pending.points.Clear(); + pending.requests.Clear(); pending = {}; - SS.ScheduleShowTW(); + if(scheduleShowTW) { + SS.ScheduleShowTW(); + } +} - // If degraded mode was enabled, we need to regenerate again to get edges back. - if(isDegraded) { - isDegraded = false; - SK.GetGroup(activeGroup)->displayDirty = true; +bool GraphicsWindow::IsFromPending(hRequest r) { + for(auto &req : pending.requests) { + if(req == r) return true; + } + return false; +} + +void GraphicsWindow::AddToPending(hRequest r) { + pending.requests.Add(&r); +} + +void GraphicsWindow::ReplacePending(hRequest before, hRequest after) { + for(auto &req : pending.requests) { + if(req == before) { + req = after; + } } } void GraphicsWindow::MouseMiddleOrRightDown(double x, double y) { - if(GraphicsEditControlIsVisible()) return; + if(window->IsEditorVisible()) return; orig.offset = offset; orig.projUp = projUp; @@ -498,26 +514,9 @@ void GraphicsWindow::MouseMiddleOrRightDown(double x, double y) { orig.startedMoving = false; } -void GraphicsWindow::ContextMenuListStyles(void) { - CreateContextSubmenu(); - Style *s; - bool empty = true; - for(s = SK.style.First(); s; s = SK.style.NextAfter(s)) { - if(s->h.v < Style::FIRST_CUSTOM) continue; - - AddContextMenuItem(s->DescriptionString().c_str(), CMNU_FIRST_STYLE + s->h.v); - empty = false; - } - - if(!empty) AddContextMenuItem(NULL, CONTEXT_SEPARATOR); - - AddContextMenuItem("No Style", CMNU_NO_STYLE); - AddContextMenuItem("Newly Created Custom Style...", CMNU_NEW_CUSTOM_STYLE); -} - void GraphicsWindow::MouseRightUp(double x, double y) { SS.extraLine.draw = false; - InvalidateGraphics(); + Invalidate(); // Don't show a context menu if the user is right-clicking the toolbar, // or if they are finishing a pan. @@ -526,15 +525,13 @@ void GraphicsWindow::MouseRightUp(double x, double y) { if(context.active) return; - if(pending.operation == DRAGGING_NEW_LINE_POINT) { - if(SS.GW.pending.suggestion != SUGGESTED_NONE) { - Constraint::Constrain(SS.GW.pending.suggestion, - Entity::NO_ENTITY, Entity::NO_ENTITY, pending.request.entity(0)); - } + if(pending.operation == Pending::DRAGGING_NEW_LINE_POINT && pending.hasSuggestion) { + Constraint::TryConstrain(SS.GW.pending.suggestion, + Entity::NO_ENTITY, Entity::NO_ENTITY, pending.request.entity(0)); } - if(pending.operation == DRAGGING_NEW_LINE_POINT || - pending.operation == DRAGGING_NEW_CUBIC_POINT) + if(pending.operation == Pending::DRAGGING_NEW_LINE_POINT || + pending.operation == Pending::DRAGGING_NEW_CUBIC_POINT) { // Special case; use a right click to stop drawing lines, since // a left click would draw another one. This is quicker and more @@ -548,6 +545,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { v = v.Plus(projRight.ScaledBy(x/scale)); v = v.Plus(projUp.ScaledBy(y/scale)); + Platform::MenuRef menu = Platform::CreateMenu(); context.active = true; if(!hover.IsEmpty()) { @@ -557,302 +555,255 @@ void GraphicsWindow::MouseRightUp(double x, double y) { GroupSelection(); bool itemsSelected = (gs.n > 0 || gs.constraints > 0); - int addAfterPoint = -1; - if(itemsSelected) { if(gs.stylables > 0) { - ContextMenuListStyles(); - AddContextMenuItem("Assign to Style", CONTEXT_SUBMENU); + Platform::MenuRef styleMenu = menu->AddSubMenu(_("Assign to Style")); + + bool empty = true; + for(const Style &s : SK.style) { + if(s.h.v < Style::FIRST_CUSTOM) continue; + + uint32_t v = s.h.v; + + styleMenu->AddItem(s.DescriptionString(), [v]() { + Style::AssignSelectionToStyle(v); + }); + empty = false; + } + + if(!empty) styleMenu->AddSeparator(); + + styleMenu->AddItem(_("No Style"), []() { + Style::AssignSelectionToStyle(0); + }); + styleMenu->AddItem(_("Newly Created Custom Style..."), [this]() { + uint32_t vs = Style::CreateCustomStyle(); + Style::AssignSelectionToStyle(vs); + ForceTextWindowShown(); + }); } if(gs.n + gs.constraints == 1) { - AddContextMenuItem("Group Info", CMNU_GROUP_INFO); + menu->AddItem(_("Group Info"), [this]() { + hGroup hg; + if(gs.entities == 1) { + hg = SK.GetEntity(gs.entity[0])->group; + } else if(gs.points == 1) { + hg = SK.GetEntity(gs.point[0])->group; + } else if(gs.constraints == 1) { + hg = SK.GetConstraint(gs.constraint[0])->group; + } else { + return; + } + ClearSelection(); + + SS.TW.GoToScreen(TextWindow::Screen::GROUP_INFO); + SS.TW.shown.group = hg; + SS.ScheduleShowTW(); + ForceTextWindowShown(); + }); } if(gs.n + gs.constraints == 1 && gs.stylables == 1) { - AddContextMenuItem("Style Info", CMNU_STYLE_INFO); + menu->AddItem(_("Style Info"), [this]() { + hStyle hs; + if(gs.entities == 1) { + hs = Style::ForEntity(gs.entity[0]); + } else if(gs.points == 1) { + hs = Style::ForEntity(gs.point[0]); + } else if(gs.constraints == 1) { + hs = SK.GetConstraint(gs.constraint[0])->GetStyle(); + } else { + return; + } + ClearSelection(); + + SS.TW.GoToScreen(TextWindow::Screen::STYLE_INFO); + SS.TW.shown.style = hs; + SS.ScheduleShowTW(); + ForceTextWindowShown(); + }); } if(gs.withEndpoints > 0) { - AddContextMenuItem("Select Edge Chain", CMNU_SELECT_CHAIN); + menu->AddItem(_("Select Edge Chain"), + []() { MenuEdit(Command::SELECT_CHAIN); }); } if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); - if(c->HasLabel() && c->type != Constraint::COMMENT) { - AddContextMenuItem("Toggle Reference Dimension", - CMNU_REFERENCE_DIM); + if(c->HasLabel() && c->type != Constraint::Type::COMMENT) { + menu->AddItem(_("Toggle Reference Dimension"), + []() { Constraint::MenuConstrain(Command::REFERENCE); }); } - if(c->type == Constraint::ANGLE || - c->type == Constraint::EQUAL_ANGLE) + if(c->type == Constraint::Type::ANGLE || + c->type == Constraint::Type::EQUAL_ANGLE) { - AddContextMenuItem("Other Supplementary Angle", - CMNU_OTHER_ANGLE); + menu->AddItem(_("Other Supplementary Angle"), + []() { Constraint::MenuConstrain(Command::OTHER_ANGLE); }); } } if(gs.constraintLabels > 0 || gs.points > 0) { - AddContextMenuItem("Snap to Grid", CMNU_SNAP_TO_GRID); + menu->AddItem(_("Snap to Grid"), + []() { MenuEdit(Command::SNAP_TO_GRID); }); } - if(gs.points == 1 && gs.point[0].isFromRequest()) { Request *r = SK.GetRequest(gs.point[0].request()); int index = r->IndexOfPoint(gs.point[0]); - if((r->type == Request::CUBIC && (index > 1 && index < r->extraPoints + 2)) || - r->type == Request::CUBIC_PERIODIC) { - AddContextMenuItem("Remove Spline Point", CMNU_REMOVE_SPLINE_PT); + if((r->type == Request::Type::CUBIC && (index > 1 && index < r->extraPoints + 2)) || + r->type == Request::Type::CUBIC_PERIODIC) { + menu->AddItem(_("Remove Spline Point"), [this, r]() { + int index = r->IndexOfPoint(gs.point[0]); + ssassert(r->extraPoints != 0, + "Expected a bezier with interior control points"); + + SS.UndoRemember(); + Entity *e = SK.GetEntity(r->h.entity(0)); + + // First, fix point-coincident constraints involving this point. + // Then, remove all other constraints, since they would otherwise + // jump to an adjacent one and mess up the bezier after generation. + FixConstraintsForPointBeingDeleted(e->point[index]); + RemoveConstraintsForPointBeingDeleted(e->point[index]); + + for(int i = index; i < MAX_POINTS_IN_ENTITY - 1; i++) { + if(e->point[i + 1].v == 0) break; + Entity *p0 = SK.GetEntity(e->point[i]); + Entity *p1 = SK.GetEntity(e->point[i + 1]); + ReplacePointInConstraints(p1->h, p0->h); + p0->PointForceTo(p1->PointGetNum()); + } + r->extraPoints--; + SS.MarkGroupDirtyByEntity(gs.point[0]); + ClearSelection(); + }); } } if(gs.entities == 1 && gs.entity[0].isFromRequest()) { Request *r = SK.GetRequest(gs.entity[0].request()); - if(r->type == Request::CUBIC || r->type == Request::CUBIC_PERIODIC) { + if(r->type == Request::Type::CUBIC || r->type == Request::Type::CUBIC_PERIODIC) { Entity *e = SK.GetEntity(gs.entity[0]); - e->GetDistance(Point2d::From(x, y)); - addAfterPoint = e->dogd.data; - if(addAfterPoint == -1) oops(); + int addAfterPoint = e->GetPositionOfPoint(GetCamera(), Point2d::From(x, y)); + ssassert(addAfterPoint != -1, "Expected a nearest bezier point to be located"); // Skip derivative point. - if(r->type == Request::CUBIC) addAfterPoint++; - AddContextMenuItem("Add Spline Point", CMNU_ADD_SPLINE_PT); + if(r->type == Request::Type::CUBIC) addAfterPoint++; + menu->AddItem(_("Add Spline Point"), [this, r, addAfterPoint, v]() { + int pointCount = r->extraPoints + + ((r->type == Request::Type::CUBIC_PERIODIC) ? 3 : 4); + if(pointCount >= MAX_POINTS_IN_ENTITY) { + Error(_("Cannot add spline point: maximum number of points reached.")); + return; + } + + SS.UndoRemember(); + r->extraPoints++; + SS.MarkGroupDirtyByEntity(gs.entity[0]); + SS.GenerateAll(SolveSpaceUI::Generate::REGEN); + + Entity *e = SK.GetEntity(r->h.entity(0)); + for(int i = MAX_POINTS_IN_ENTITY; i > addAfterPoint + 1; i--) { + Entity *p0 = SK.entity.FindByIdNoOops(e->point[i]); + if(p0 == NULL) continue; + Entity *p1 = SK.GetEntity(e->point[i - 1]); + ReplacePointInConstraints(p1->h, p0->h); + p0->PointForceTo(p1->PointGetNum()); + } + Entity *p = SK.GetEntity(e->point[addAfterPoint + 1]); + p->PointForceTo(v); + SS.MarkGroupDirtyByEntity(gs.entity[0]); + ClearSelection(); + }); } } + if(gs.entities == gs.n) { + menu->AddItem(_("Toggle Construction"), + []() { MenuRequest(Command::CONSTRUCTION); }); + } if(gs.points == 1) { Entity *p = SK.GetEntity(gs.point[0]); Constraint *c; IdList *lc = &(SK.constraint); for(c = lc->First(); c; c = lc->NextAfter(c)) { - if(c->type != Constraint::POINTS_COINCIDENT) continue; - if(c->ptA.v == p->h.v || c->ptB.v == p->h.v) { + if(c->type != Constraint::Type::POINTS_COINCIDENT) continue; + if(c->ptA == p->h || c->ptB == p->h) { break; } } if(c) { - AddContextMenuItem("Delete Point-Coincident Constraint", - CMNU_DEL_COINCIDENT); + menu->AddItem(_("Delete Point-Coincident Constraint"), [this, p]() { + if(!p->IsPoint()) return; + + SS.UndoRemember(); + SK.constraint.ClearTags(); + Constraint *c; + for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { + if(c->type != Constraint::Type::POINTS_COINCIDENT) continue; + if(c->ptA == p->h || c->ptB == p->h) { + c->tag = 1; + } + } + SK.constraint.RemoveTagged(); + ClearSelection(); + }); } } - AddContextMenuItem(NULL, CONTEXT_SEPARATOR); + menu->AddSeparator(); if(LockedInWorkplane()) { - AddContextMenuItem("Cut", CMNU_CUT_SEL); - AddContextMenuItem("Copy", CMNU_COPY_SEL); + menu->AddItem(_("Cut"), + []() { MenuClipboard(Command::CUT); }); + menu->AddItem(_("Copy"), + []() { MenuClipboard(Command::COPY); }); } + } else { + menu->AddItem(_("Select All"), + []() { MenuEdit(Command::SELECT_ALL); }); } - if((SS.clipboard.r.n > 0 || SS.clipboard.c.n > 0) && LockedInWorkplane()) { - AddContextMenuItem("Paste", CMNU_PASTE); - AddContextMenuItem("Paste Transformed...", CMNU_PASTE_XFRM); + if((!SS.clipboard.r.IsEmpty() || !SS.clipboard.c.IsEmpty()) && LockedInWorkplane()) { + menu->AddItem(_("Paste"), + []() { MenuClipboard(Command::PASTE); }); + menu->AddItem(_("Paste Transformed..."), + []() { MenuClipboard(Command::PASTE_TRANSFORM); }); } if(itemsSelected) { - AddContextMenuItem("Delete", CMNU_DELETE_SEL); - AddContextMenuItem(NULL, CONTEXT_SEPARATOR); - AddContextMenuItem("Unselect All", CMNU_UNSELECT_ALL); + menu->AddItem(_("Delete"), + []() { MenuClipboard(Command::DELETE); }); + menu->AddSeparator(); + menu->AddItem(_("Unselect All"), + []() { MenuEdit(Command::UNSELECT_ALL); }); } // If only one item is selected, then it must be the one that we just // selected from the hovered item; in which case unselect all and hovered // are equivalent. if(!hover.IsEmpty() && selection.n > 1) { - AddContextMenuItem("Unselect Hovered", CMNU_UNSELECT_HOVERED); - } - - int ret = ShowContextMenu(); - switch(ret) { - case CMNU_UNSELECT_ALL: - MenuEdit(MNU_UNSELECT_ALL); - break; - - case CMNU_UNSELECT_HOVERED: + menu->AddItem(_("Unselect Hovered"), [this] { if(!hover.IsEmpty()) { - MakeUnselected(&hover, true); - } - break; - - case CMNU_SELECT_CHAIN: - MenuEdit(MNU_SELECT_CHAIN); - break; - - case CMNU_CUT_SEL: - MenuClipboard(MNU_CUT); - break; - - case CMNU_COPY_SEL: - MenuClipboard(MNU_COPY); - break; - - case CMNU_PASTE: - MenuClipboard(MNU_PASTE); - break; - - case CMNU_PASTE_XFRM: - MenuClipboard(MNU_PASTE_TRANSFORM); - break; - - case CMNU_DELETE_SEL: - MenuClipboard(MNU_DELETE); - break; - - case CMNU_REFERENCE_DIM: - Constraint::MenuConstrain(MNU_REFERENCE); - break; - - case CMNU_OTHER_ANGLE: - Constraint::MenuConstrain(MNU_OTHER_ANGLE); - break; - - case CMNU_DEL_COINCIDENT: { - SS.UndoRemember(); - if(!gs.point[0].v) break; - Entity *p = SK.GetEntity(gs.point[0]); - if(!p->IsPoint()) break; - - SK.constraint.ClearTags(); - Constraint *c; - for(c = SK.constraint.First(); c; c = SK.constraint.NextAfter(c)) { - if(c->type != Constraint::POINTS_COINCIDENT) continue; - if(c->ptA.v == p->h.v || c->ptB.v == p->h.v) { - c->tag = 1; - } + MakeUnselected(&hover, /*coincidentPointTrick=*/true); } - SK.constraint.RemoveTagged(); - ClearSelection(); - break; - } - - case CMNU_SNAP_TO_GRID: - MenuEdit(MNU_SNAP_TO_GRID); - break; - - case CMNU_REMOVE_SPLINE_PT: { - hRequest hr = gs.point[0].request(); - Request *r = SK.GetRequest(hr); - - int index = r->IndexOfPoint(gs.point[0]); - if(r->extraPoints == 0) oops(); - - SS.UndoRemember(); - Entity *e = SK.GetEntity(r->h.entity(0)); - - // First, fix point-coincident constraints involving this point. - // Then, remove all other constraints, since they would otherwise - // jump to an adjacent one and mess up the bezier after generation. - FixConstraintsForPointBeingDeleted(e->point[index]); - RemoveConstraintsForPointBeingDeleted(e->point[index]); - - for(int i = index; i < MAX_POINTS_IN_ENTITY - 1; i++) { - if(e->point[i + 1].v == 0) break; - Entity *p0 = SK.GetEntity(e->point[i]); - Entity *p1 = SK.GetEntity(e->point[i + 1]); - ReplacePointInConstraints(p1->h, p0->h); - p0->PointForceTo(p1->PointGetNum()); - } - r->extraPoints--; - SS.MarkGroupDirtyByEntity(gs.point[0]); - SS.ScheduleGenerateAll(); - ClearSelection(); - break; - } - - case CMNU_ADD_SPLINE_PT: { - hRequest hr = gs.entity[0].request(); - Request *r = SK.GetRequest(hr); - - int pointCount = r->extraPoints + ((r->type == Request::CUBIC_PERIODIC) ? 3 : 4); - if(pointCount < MAX_POINTS_IN_ENTITY) { - SS.UndoRemember(); - r->extraPoints++; - SS.MarkGroupDirtyByEntity(gs.entity[0]); - SS.GenerateAll(SolveSpaceUI::GENERATE_REGEN); - - Entity *e = SK.GetEntity(r->h.entity(0)); - for(int i = MAX_POINTS_IN_ENTITY; i > addAfterPoint + 1; i--) { - Entity *p0 = SK.entity.FindByIdNoOops(e->point[i]); - if(p0 == NULL) continue; - Entity *p1 = SK.GetEntity(e->point[i - 1]); - ReplacePointInConstraints(p1->h, p0->h); - p0->PointForceTo(p1->PointGetNum()); - } - Entity *p = SK.GetEntity(e->point[addAfterPoint + 1]); - p->PointForceTo(v); - SS.MarkGroupDirtyByEntity(gs.entity[0]); - SS.ScheduleGenerateAll(); - ClearSelection(); - } else { - Error("Cannot add spline point: maximum number of points reached."); - } - break; - } - - case CMNU_GROUP_INFO: { - hGroup hg; - if(gs.entities == 1) { - hg = SK.GetEntity(gs.entity[0])->group; - } else if(gs.points == 1) { - hg = SK.GetEntity(gs.point[0])->group; - } else if(gs.constraints == 1) { - hg = SK.GetConstraint(gs.constraint[0])->group; - } else { - break; - } - ClearSelection(); - - SS.TW.GoToScreen(TextWindow::SCREEN_GROUP_INFO); - SS.TW.shown.group = hg; - SS.ScheduleShowTW(); - ForceTextWindowShown(); - break; - } - - case CMNU_STYLE_INFO: { - hStyle hs; - if(gs.entities == 1) { - hs = Style::ForEntity(gs.entity[0]); - } else if(gs.points == 1) { - hs = Style::ForEntity(gs.point[0]); - } else if(gs.constraints == 1) { - hs = SK.GetConstraint(gs.constraint[0])->GetStyle(); - } else { - break; - } - ClearSelection(); - - SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO); - SS.TW.shown.style = hs; - SS.ScheduleShowTW(); - ForceTextWindowShown(); - break; - } - - case CMNU_NEW_CUSTOM_STYLE: { - uint32_t v = Style::CreateCustomStyle(); - Style::AssignSelectionToStyle(v); - ForceTextWindowShown(); - break; - } - - case CMNU_NO_STYLE: - Style::AssignSelectionToStyle(0); - break; + }); + } - default: - if(ret >= CMNU_FIRST_STYLE) { - Style::AssignSelectionToStyle(ret - CMNU_FIRST_STYLE); - } else { - // otherwise it was cancelled, so do nothing - contextMenuCancelTime = GetMilliseconds(); - } - break; + if(itemsSelected) { + menu->AddSeparator(); + menu->AddItem(_("Zoom to Fit"), + []() { MenuView(Command::ZOOM_TO_FIT); }); } + menu->PopUp(); + context.active = false; SS.ScheduleShowTW(); } -hRequest GraphicsWindow::AddRequest(int type) { - return AddRequest(type, true); +hRequest GraphicsWindow::AddRequest(Request::Type type) { + return AddRequest(type, /*rememberForUndo=*/true); } -hRequest GraphicsWindow::AddRequest(int type, bool rememberForUndo) { +hRequest GraphicsWindow::AddRequest(Request::Type type, bool rememberForUndo) { if(rememberForUndo) SS.UndoRemember(); Request r = {}; r.group = activeGroup; Group *g = SK.GetGroup(activeGroup); - if(g->type == Group::DRAWING_3D || g->type == Group::DRAWING_WORKPLANE) { + if(g->type == Group::Type::DRAWING_3D || g->type == Group::Type::DRAWING_WORKPLANE) { r.construction = false; } else { r.construction = true; @@ -865,26 +816,58 @@ hRequest GraphicsWindow::AddRequest(int type, bool rememberForUndo) { // place this request's entities where the mouse is can do so. But // we mustn't try to solve until reasonable values have been supplied // for these new parameters, or else we'll get a numerical blowup. - SS.GenerateAll(SolveSpaceUI::GENERATE_REGEN); + r.Generate(&SK.entity, &SK.param); SS.MarkGroupDirty(r.group); return r.h; } -bool GraphicsWindow::ConstrainPointByHovered(hEntity pt) { +Vector GraphicsWindow::SnapToEntityByScreenPoint(Point2d pp, hEntity he) { + Entity *e = SK.GetEntity(he); + if(e->IsPoint()) return e->PointGetNum(); + SEdgeList *edges = e->GetOrGenerateEdges(); + + double minD = -1.0f; + double k; + const SEdge *edge = NULL; + for(const auto &e : edges->l) { + Point2d p0 = ProjectPoint(e.a); + Point2d p1 = ProjectPoint(e.b); + Point2d dir = p1.Minus(p0); + double d = pp.DistanceToLine(p0, dir, /*asSegment=*/true); + if(minD > 0.0 && d > minD) continue; + minD = d; + k = pp.Minus(p0).Dot(dir) / dir.Dot(dir); + edge = &e; + } + if(edge == NULL) return UnProjectPoint(pp); + return edge->a.Plus(edge->b.Minus(edge->a).ScaledBy(k)); +} + +bool GraphicsWindow::ConstrainPointByHovered(hEntity pt, const Point2d *projected) { if(!hover.entity.v) return false; + Entity *point = SK.GetEntity(pt); Entity *e = SK.GetEntity(hover.entity); if(e->IsPoint()) { + point->PointForceTo(e->PointGetNum()); Constraint::ConstrainCoincident(e->h, pt); return true; } if(e->IsCircle()) { - Constraint::Constrain(Constraint::PT_ON_CIRCLE, + if(projected != NULL) { + Vector snapPos = SnapToEntityByScreenPoint(*projected, e->h); + point->PointForceTo(snapPos); + } + Constraint::Constrain(Constraint::Type::PT_ON_CIRCLE, pt, Entity::NO_ENTITY, e->h); return true; } - if(e->type == Entity::LINE_SEGMENT) { - Constraint::Constrain(Constraint::PT_ON_LINE, + if(e->type == Entity::Type::LINE_SEGMENT) { + if(projected != NULL) { + Vector snapPos = SnapToEntityByScreenPoint(*projected, e->h); + point->PointForceTo(snapPos); + } + Constraint::Constrain(Constraint::Type::PT_ON_LINE, pt, Entity::NO_ENTITY, e->h); return true; } @@ -892,13 +875,67 @@ bool GraphicsWindow::ConstrainPointByHovered(hEntity pt) { return false; } -void GraphicsWindow::MouseLeftDown(double mx, double my) { +bool GraphicsWindow::MouseEvent(Platform::MouseEvent event) { + using Platform::MouseEvent; + + double width, height; + window->GetContentSize(&width, &height); + + event.x = event.x - width / 2; + event.y = height / 2 - event.y; + + switch(event.type) { + case MouseEvent::Type::MOTION: + this->MouseMoved(event.x, event.y, + event.button == MouseEvent::Button::LEFT, + event.button == MouseEvent::Button::MIDDLE, + event.button == MouseEvent::Button::RIGHT, + event.shiftDown, + event.controlDown); + break; + + case MouseEvent::Type::PRESS: + if(event.button == MouseEvent::Button::LEFT) { + this->MouseLeftDown(event.x, event.y, event.shiftDown, event.controlDown); + } else if(event.button == MouseEvent::Button::MIDDLE || + event.button == MouseEvent::Button::RIGHT) { + this->MouseMiddleOrRightDown(event.x, event.y); + } + break; + + case MouseEvent::Type::DBL_PRESS: + if(event.button == MouseEvent::Button::LEFT) { + this->MouseLeftDoubleClick(event.x, event.y); + } + break; + + case MouseEvent::Type::RELEASE: + if(event.button == MouseEvent::Button::LEFT) { + this->MouseLeftUp(event.x, event.y, event.shiftDown, event.controlDown); + } else if(event.button == MouseEvent::Button::RIGHT) { + this->MouseRightUp(event.x, event.y); + } + break; + + case MouseEvent::Type::SCROLL_VERT: + this->MouseScroll(event.x, event.y, (int)event.scrollDelta); + break; + + case MouseEvent::Type::LEAVE: + this->MouseLeave(); + break; + } + + return true; +} + +void GraphicsWindow::MouseLeftDown(double mx, double my, bool shiftDown, bool ctrlDown) { orig.mouseDown = true; - if(GraphicsEditControlIsVisible()) { + if(window->IsEditorVisible()) { orig.mouse = Point2d::From(mx, my); orig.mouseOnButtonDown = orig.mouse; - HideGraphicsEditControl(); + window->HideEditor(); return; } SS.TW.HideEditControl(); @@ -908,13 +945,16 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { } // This will be clobbered by MouseMoved below. - SuggestedConstraint constraintSuggestion = SS.GW.pending.suggestion; + bool hasConstraintSuggestion = pending.hasSuggestion; + Constraint::Type constraintSuggestion = pending.suggestion; // Make sure the hover is up to date. - MouseMoved(mx, my, false, false, false, false, false); + MouseMoved(mx, my, /*leftDown=*/false, /*middleDown=*/false, /*rightDown=*/false, + /*shiftDown=*/false, /*ctrlDown=*/false); orig.mouse.x = mx; orig.mouse.y = my; orig.mouseOnButtonDown = orig.mouse; + Point2d mouse = Point2d::From(mx, my); // The current mouse location Vector v = offset.ScaledBy(-1); @@ -924,185 +964,226 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { hRequest hr = {}; hConstraint hc = {}; switch(pending.operation) { - case MNU_DATUM_POINT: - hr = AddRequest(Request::DATUM_POINT); - SK.GetEntity(hr.entity(0))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(0)); - - ClearSuper(); - break; + case Pending::COMMAND: + switch(pending.command) { + case Command::DATUM_POINT: + hr = AddRequest(Request::Type::DATUM_POINT); + SK.GetEntity(hr.entity(0))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(0), &mouse); + + ClearSuper(); + break; - case MNU_LINE_SEGMENT: - case MNU_CONSTR_SEGMENT: - hr = AddRequest(Request::LINE_SEGMENT); - SK.GetRequest(hr)->construction = (pending.operation == MNU_CONSTR_SEGMENT); - SK.GetEntity(hr.entity(1))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(1)); + case Command::LINE_SEGMENT: + case Command::CONSTR_SEGMENT: + hr = AddRequest(Request::Type::LINE_SEGMENT); + SK.GetRequest(hr)->construction = (pending.command == Command::CONSTR_SEGMENT); + SK.GetEntity(hr.entity(1))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(1), &mouse); + + ClearSuper(); + AddToPending(hr); + + pending.operation = Pending::DRAGGING_NEW_LINE_POINT; + pending.request = hr; + pending.point = hr.entity(2); + pending.description = _("click next point of line, or press Esc"); + SK.GetEntity(pending.point)->PointForceTo(v); + break; - ClearSuper(); + case Command::RECTANGLE: { + if(!SS.GW.LockedInWorkplane()) { + Error(_("Can't draw rectangle in 3d; first, activate a workplane " + "with Sketch -> In Workplane.")); + ClearSuper(); + break; + } + hRequest lns[4]; + int i; + SS.UndoRemember(); + for(i = 0; i < 4; i++) { + lns[i] = AddRequest(Request::Type::LINE_SEGMENT, /*rememberForUndo=*/false); + AddToPending(lns[i]); + } + for(i = 0; i < 4; i++) { + Constraint::ConstrainCoincident( + lns[i].entity(1), lns[(i+1)%4].entity(2)); + SK.GetEntity(lns[i].entity(1))->PointForceTo(v); + SK.GetEntity(lns[i].entity(2))->PointForceTo(v); + } + for(i = 0; i < 4; i++) { + Constraint::Constrain( + (i % 2) ? Constraint::Type::HORIZONTAL : Constraint::Type::VERTICAL, + Entity::NO_ENTITY, Entity::NO_ENTITY, + lns[i].entity(0)); + } + if(ConstrainPointByHovered(lns[2].entity(1), &mouse)) { + Vector pos = SK.GetEntity(lns[2].entity(1))->PointGetNum(); + for(i = 0; i < 4; i++) { + SK.GetEntity(lns[i].entity(1))->PointForceTo(pos); + SK.GetEntity(lns[i].entity(2))->PointForceTo(pos); + } + } - pending.operation = DRAGGING_NEW_LINE_POINT; - pending.request = hr; - pending.point = hr.entity(2); - pending.description = "click next point of line, or press Esc"; - SK.GetEntity(pending.point)->PointForceTo(v); - break; + pending.operation = Pending::DRAGGING_NEW_POINT; + pending.point = lns[1].entity(2); + pending.description = _("click to place other corner of rectangle"); + hr = lns[0]; + break; + } + case Command::CIRCLE: + hr = AddRequest(Request::Type::CIRCLE); + // Centered where we clicked + SK.GetEntity(hr.entity(1))->PointForceTo(v); + // Normal to the screen + SK.GetEntity(hr.entity(32))->NormalForceTo( + Quaternion::From(SS.GW.projRight, SS.GW.projUp)); + // Initial radius zero + SK.GetEntity(hr.entity(64))->DistanceForceTo(0); + + ConstrainPointByHovered(hr.entity(1), &mouse); + + ClearSuper(); + + pending.operation = Pending::DRAGGING_NEW_RADIUS; + pending.circle = hr.entity(0); + pending.description = _("click to set radius"); + break; - case MNU_RECTANGLE: { - if(!SS.GW.LockedInWorkplane()) { - Error("Can't draw rectangle in 3d; select a workplane first."); - ClearSuper(); - break; - } - hRequest lns[4]; - int i; - SS.UndoRemember(); - for(i = 0; i < 4; i++) { - lns[i] = AddRequest(Request::LINE_SEGMENT, false); - } - for(i = 0; i < 4; i++) { - Constraint::ConstrainCoincident( - lns[i].entity(1), lns[(i+1)%4].entity(2)); - SK.GetEntity(lns[i].entity(1))->PointForceTo(v); - SK.GetEntity(lns[i].entity(2))->PointForceTo(v); - } - for(i = 0; i < 4; i++) { - Constraint::Constrain( - (i % 2) ? Constraint::HORIZONTAL : Constraint::VERTICAL, - Entity::NO_ENTITY, Entity::NO_ENTITY, - lns[i].entity(0)); - } - ConstrainPointByHovered(lns[2].entity(1)); + case Command::ARC: { + if(!SS.GW.LockedInWorkplane()) { + Error(_("Can't draw arc in 3d; first, activate a workplane " + "with Sketch -> In Workplane.")); + ClearPending(); + break; + } + hr = AddRequest(Request::Type::ARC_OF_CIRCLE); + // This fudge factor stops us from immediately failing to solve + // because of the arc's implicit (equal radius) tangent. + Vector adj = SS.GW.projRight.WithMagnitude(2/SS.GW.scale); + SK.GetEntity(hr.entity(1))->PointForceTo(v.Minus(adj)); + SK.GetEntity(hr.entity(2))->PointForceTo(v); + SK.GetEntity(hr.entity(3))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(2), &mouse); + + ClearSuper(); + AddToPending(hr); + + pending.operation = Pending::DRAGGING_NEW_ARC_POINT; + pending.point = hr.entity(3); + pending.description = _("click to place point"); + break; + } + case Command::CUBIC: + hr = AddRequest(Request::Type::CUBIC); + SK.GetEntity(hr.entity(1))->PointForceTo(v); + SK.GetEntity(hr.entity(2))->PointForceTo(v); + SK.GetEntity(hr.entity(3))->PointForceTo(v); + SK.GetEntity(hr.entity(4))->PointForceTo(v); + ConstrainPointByHovered(hr.entity(1), &mouse); + + ClearSuper(); + AddToPending(hr); + + pending.operation = Pending::DRAGGING_NEW_CUBIC_POINT; + pending.point = hr.entity(4); + pending.description = _("click next point of cubic, or press Esc"); + break; - pending.operation = DRAGGING_NEW_POINT; - pending.point = lns[1].entity(2); - pending.description = "click to place other corner of rectangle"; - hr = lns[0]; - break; - } - case MNU_CIRCLE: - hr = AddRequest(Request::CIRCLE); - // Centered where we clicked - SK.GetEntity(hr.entity(1))->PointForceTo(v); - // Normal to the screen - SK.GetEntity(hr.entity(32))->NormalForceTo( - Quaternion::From(SS.GW.projRight, SS.GW.projUp)); - // Initial radius zero - SK.GetEntity(hr.entity(64))->DistanceForceTo(0); - - ConstrainPointByHovered(hr.entity(1)); - - ClearSuper(); - - pending.operation = DRAGGING_NEW_RADIUS; - pending.circle = hr.entity(0); - pending.description = "click to set radius"; - break; + case Command::WORKPLANE: + if(LockedInWorkplane()) { + Error(_("Sketching in a workplane already; sketch in 3d before " + "creating new workplane.")); + ClearSuper(); + break; + } + hr = AddRequest(Request::Type::WORKPLANE); + SK.GetEntity(hr.entity(1))->PointForceTo(v); + SK.GetEntity(hr.entity(32))->NormalForceTo( + Quaternion::From(SS.GW.projRight, SS.GW.projUp)); + ConstrainPointByHovered(hr.entity(1), &mouse); - case MNU_ARC: { - if(!SS.GW.LockedInWorkplane()) { - Error("Can't draw arc in 3d; select a workplane first."); - ClearPending(); - break; - } - hr = AddRequest(Request::ARC_OF_CIRCLE); - // This fudge factor stops us from immediately failing to solve - // because of the arc's implicit (equal radius) tangent. - Vector adj = SS.GW.projRight.WithMagnitude(2/SS.GW.scale); - SK.GetEntity(hr.entity(1))->PointForceTo(v.Minus(adj)); - SK.GetEntity(hr.entity(2))->PointForceTo(v); - SK.GetEntity(hr.entity(3))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(2)); - - ClearSuper(); - - pending.operation = DRAGGING_NEW_ARC_POINT; - pending.point = hr.entity(3); - pending.description = "click to place point"; - break; - } - case MNU_CUBIC: - hr = AddRequest(Request::CUBIC); - SK.GetEntity(hr.entity(1))->PointForceTo(v); - SK.GetEntity(hr.entity(2))->PointForceTo(v); - SK.GetEntity(hr.entity(3))->PointForceTo(v); - SK.GetEntity(hr.entity(4))->PointForceTo(v); - ConstrainPointByHovered(hr.entity(1)); - - ClearSuper(); - - pending.operation = DRAGGING_NEW_CUBIC_POINT; - pending.point = hr.entity(4); - pending.description = "click next point of cubic, or press Esc"; - break; + ClearSuper(); + break; - case MNU_WORKPLANE: - if(LockedInWorkplane()) { - Error("Sketching in a workplane already; sketch in 3d before " - "creating new workplane."); - ClearSuper(); - break; - } - hr = AddRequest(Request::WORKPLANE); - SK.GetEntity(hr.entity(1))->PointForceTo(v); - SK.GetEntity(hr.entity(32))->NormalForceTo( - Quaternion::From(SS.GW.projRight, SS.GW.projUp)); - ConstrainPointByHovered(hr.entity(1)); + case Command::TTF_TEXT: { + if(!SS.GW.LockedInWorkplane()) { + Error(_("Can't draw text in 3d; first, activate a workplane " + "with Sketch -> In Workplane.")); + ClearSuper(); + break; + } + hr = AddRequest(Request::Type::TTF_TEXT); + AddToPending(hr); + Request *r = SK.GetRequest(hr); + r->str = "Abc"; + r->font = "BitstreamVeraSans-Roman-builtin.ttf"; + + for(int i = 1; i <= 4; i++) { + SK.GetEntity(hr.entity(i))->PointForceTo(v); + } - ClearSuper(); - break; + pending.operation = Pending::DRAGGING_NEW_POINT; + pending.point = hr.entity(3); + pending.description = _("click to place bottom right of text"); + break; + } - case MNU_TTF_TEXT: { - if(!SS.GW.LockedInWorkplane()) { - Error("Can't draw text in 3d; select a workplane first."); - ClearSuper(); - break; - } - hr = AddRequest(Request::TTF_TEXT); - Request *r = SK.GetRequest(hr); - r->str = "Abc"; - r->font = "arial.ttf"; + case Command::IMAGE: { + if(!SS.GW.LockedInWorkplane()) { + Error(_("Can't draw image in 3d; first, activate a workplane " + "with Sketch -> In Workplane.")); + ClearSuper(); + break; + } + hr = AddRequest(Request::Type::IMAGE); + AddToPending(hr); + Request *r = SK.GetRequest(hr); + r->file = pending.filename; - SK.GetEntity(hr.entity(1))->PointForceTo(v); - SK.GetEntity(hr.entity(2))->PointForceTo(v); + for(int i = 1; i <= 4; i++) { + SK.GetEntity(hr.entity(i))->PointForceTo(v); + } - pending.operation = DRAGGING_NEW_POINT; - pending.point = hr.entity(2); - pending.description = "click to place bottom left of text"; - break; - } + pending.operation = Pending::DRAGGING_NEW_POINT; + pending.point = hr.entity(3); + pending.description = "click to place bottom right of image"; + break; + } - case MNU_COMMENT: { - ClearSuper(); - Constraint c = {}; - c.group = SS.GW.activeGroup; - c.workplane = SS.GW.ActiveWorkplane(); - c.type = Constraint::COMMENT; - c.disp.offset = v; - c.comment = "NEW COMMENT -- DOUBLE-CLICK TO EDIT"; - hc = Constraint::AddConstraint(&c); + case Command::COMMENT: { + ClearSuper(); + Constraint c = {}; + c.group = SS.GW.activeGroup; + c.workplane = SS.GW.ActiveWorkplane(); + c.type = Constraint::Type::COMMENT; + c.disp.offset = v; + c.comment = _("NEW COMMENT -- DOUBLE-CLICK TO EDIT"); + hc = Constraint::AddConstraint(&c); + break; + } + default: ssassert(false, "Unexpected pending menu id"); + } break; - } - case DRAGGING_RADIUS: - case DRAGGING_NEW_POINT: - // The MouseMoved event has already dragged it as desired. + case Pending::DRAGGING_RADIUS: ClearPending(); break; - case DRAGGING_NEW_ARC_POINT: - ConstrainPointByHovered(pending.point); + case Pending::DRAGGING_NEW_POINT: + case Pending::DRAGGING_NEW_ARC_POINT: + ConstrainPointByHovered(pending.point, &mouse); ClearPending(); break; - case DRAGGING_NEW_CUBIC_POINT: { + case Pending::DRAGGING_NEW_CUBIC_POINT: { hRequest hr = pending.point.request(); Request *r = SK.GetRequest(hr); - if(hover.entity.v == hr.entity(1).v && r->extraPoints >= 2) { + if(hover.entity == hr.entity(1) && r->extraPoints >= 2) { // They want the endpoints coincident, which means a periodic // spline instead. - r->type = Request::CUBIC_PERIODIC; + r->type = Request::Type::CUBIC_PERIODIC; // Remove the off-curve control points, which are no longer // needed here; so move [2,ep+1] down, skipping first pt. int i; @@ -1116,12 +1197,11 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { r->extraPoints -= 2; // And we're done. SS.MarkGroupDirty(r->group); - SS.ScheduleGenerateAll(); ClearPending(); break; } - if(ConstrainPointByHovered(pending.point)) { + if(ConstrainPointByHovered(pending.point, &mouse)) { ClearPending(); break; } @@ -1133,7 +1213,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { } (SK.GetRequest(hr)->extraPoints)++; - SS.GenerateAll(SolveSpaceUI::GENERATE_REGEN); + SS.GenerateAll(SolveSpaceUI::Generate::REGEN); int ep = r->extraPoints; Vector last = SK.GetEntity(hr.entity(3+ep))->PointGetNum(); @@ -1145,13 +1225,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { break; } - case DRAGGING_NEW_LINE_POINT: { - // Constrain the line segment horizontal or vertical if close enough - if(constraintSuggestion != SUGGESTED_NONE) { - Constraint::Constrain(constraintSuggestion, - Entity::NO_ENTITY, Entity::NO_ENTITY, pending.request.entity(0)); - } - + case Pending::DRAGGING_NEW_LINE_POINT: { if(hover.entity.v) { Entity *e = SK.GetEntity(hover.entity); if(e->IsPoint()) { @@ -1169,15 +1243,23 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { } } - if(ConstrainPointByHovered(pending.point)) { + bool doneDragging = ConstrainPointByHovered(pending.point, &mouse); + + // Constrain the line segment horizontal or vertical if close enough + if(hasConstraintSuggestion) { + Constraint::TryConstrain(constraintSuggestion, + Entity::NO_ENTITY, Entity::NO_ENTITY, pending.request.entity(0)); + } + + if(doneDragging) { ClearPending(); break; } // Create a new line segment, so that we continue drawing. - hRequest hr = AddRequest(Request::LINE_SEGMENT); + hRequest hr = AddRequest(Request::Type::LINE_SEGMENT); + ReplacePending(pending.request, hr); SK.GetRequest(hr)->construction = SK.GetRequest(pending.request)->construction; - SK.GetEntity(hr.entity(1))->PointForceTo(v); // Displace the second point of the new line segment slightly, // to avoid creating zero-length edge warnings. SK.GetEntity(hr.entity(2))->PointForceTo( @@ -1185,22 +1267,28 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { // Constrain the line segments to share an endpoint Constraint::ConstrainCoincident(pending.point, hr.entity(1)); + Vector pendingPos = SK.GetEntity(pending.point)->PointGetNum(); + SK.GetEntity(hr.entity(1))->PointForceTo(pendingPos); // And drag an endpoint of the new line segment - pending.operation = DRAGGING_NEW_LINE_POINT; + pending.operation = Pending::DRAGGING_NEW_LINE_POINT; pending.request = hr; pending.point = hr.entity(2); - pending.description = "click next point of line, or press Esc"; + pending.description = _("click next point of line, or press Esc"); break; } - case 0: + case Pending::NONE: default: ClearPending(); if(!hover.IsEmpty()) { - hoverWasSelectedOnMousedown = IsSelected(&hover); - MakeSelected(&hover); + if(!ctrlDown) { + hoverWasSelectedOnMousedown = IsSelected(&hover); + MakeSelected(&hover); + } else { + MakeUnselected(&hover, /*coincidentPointTrick=*/true); + } } break; } @@ -1208,52 +1296,42 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { // Activate group with newly created request/constraint Group *g = NULL; if(hr.v != 0) { - Request *req = SK.GetRequest(hr); - g = SK.GetGroup(req->group); + g = SK.GetGroup(SK.GetRequest(hr)->group); } if(hc.v != 0) { - Constraint *c = SK.GetConstraint(hc); - g = SK.GetGroup(c->group); + g = SK.GetGroup(SK.GetConstraint(hc)->group); } if(g != NULL) { g->visible = true; } SS.ScheduleShowTW(); - InvalidateGraphics(); + Invalidate(); } -void GraphicsWindow::MouseLeftUp(double mx, double my) { +void GraphicsWindow::MouseLeftUp(double mx, double my, bool shiftDown, bool ctrlDown) { orig.mouseDown = false; hoverWasSelectedOnMousedown = false; switch(pending.operation) { - case DRAGGING_POINTS: + case Pending::DRAGGING_POINTS: SS.extraLine.draw = false; // fall through - case DRAGGING_CONSTRAINT: - case DRAGGING_NORMAL: - case DRAGGING_RADIUS: + case Pending::DRAGGING_CONSTRAINT: + case Pending::DRAGGING_NORMAL: + case Pending::DRAGGING_RADIUS: ClearPending(); - InvalidateGraphics(); + Invalidate(); break; - case DRAGGING_MARQUEE: + case Pending::DRAGGING_MARQUEE: SelectByMarquee(); ClearPending(); - InvalidateGraphics(); + Invalidate(); break; - case 0: - // We need to clear the selection here, and not in the mouse down - // event, since a mouse down without anything hovered could also - // be the start of marquee selection. But don't do that on the - // left click to cancel a context menu. The time delay is an ugly - // hack. - if(hover.IsEmpty() && - (contextMenuCancelTime == 0 || - (GetMilliseconds() - contextMenuCancelTime) > 200)) - { + case Pending::NONE: + if(hover.IsEmpty() && !ctrlDown) { ClearSelection(); } break; @@ -1263,96 +1341,101 @@ void GraphicsWindow::MouseLeftUp(double mx, double my) { } } -void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { - if(GraphicsEditControlIsVisible()) return; - SS.TW.HideEditControl(); - - if(hover.constraint.v) { - constraintBeingEdited = hover.constraint; - ClearSuper(); +void GraphicsWindow::EditConstraint(hConstraint constraint) { + constraintBeingEdited = constraint; + ClearSuper(); - Constraint *c = SK.GetConstraint(constraintBeingEdited); - if(!c->HasLabel()) { - // Not meaningful to edit a constraint without a dimension - return; - } - if(c->reference) { - // Not meaningful to edit a reference dimension - return; - } - - Vector p3 = c->GetLabelPos(); - Point2d p2 = ProjectPoint(p3); + Constraint *c = SK.GetConstraint(constraintBeingEdited); + if(!c->HasLabel()) { + // Not meaningful to edit a constraint without a dimension + return; + } + if(c->reference) { + // Not meaningful to edit a reference dimension + return; + } - std::string editValue; - int editMinWidthChar; - switch(c->type) { - case Constraint::COMMENT: - editValue = c->comment; - editMinWidthChar = 30; - break; + Vector p3 = c->GetLabelPos(GetCamera()); + Point2d p2 = ProjectPoint(p3); - case Constraint::ANGLE: - case Constraint::LENGTH_RATIO: - editValue = ssprintf("%.3f", c->valA); - editMinWidthChar = 5; - break; + std::string editValue; + std::string editPlaceholder; + switch(c->type) { + case Constraint::Type::COMMENT: + editValue = c->comment; + editPlaceholder = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + break; - default: { - double v = fabs(c->valA); + default: { + double value = fabs(c->valA); - // If displayed as radius, also edit as radius. - if(c->type == Constraint::DIAMETER && c->other) - v /= 2; + // If displayed as radius, also edit as radius. + if(c->type == Constraint::Type::DIAMETER && c->other) + value /= 2; - std::string def = SS.MmToString(v); - double eps = 1e-12; - if(fabs(SS.StringToMm(def) - v) < eps) { - // Show value with default number of digits after decimal, - // which is at least enough to represent it exactly. - editValue = def; - } else { - // Show value with as many digits after decimal as - // required to represent it exactly, up to 10. - v /= SS.MmPerUnit(); - int i; - for(i = 0; i <= 10; i++) { - editValue = ssprintf("%.*f", i, v); - if(fabs(std::stod(editValue) - v) < eps) break; - } - } - editMinWidthChar = 5; - break; + // Try showing value with default number of digits after decimal first. + if(c->type == Constraint::Type::LENGTH_RATIO) { + editValue = ssprintf("%.3f", value); + } else if(c->type == Constraint::Type::ANGLE) { + editValue = SS.DegreeToString(value); + } else { + editValue = SS.MmToString(value); + value /= SS.MmPerUnit(); } + // If that's not enough to represent it exactly, show the value with as many + // digits after decimal as required, up to 10. + int digits = 0; + while(fabs(std::stod(editValue) - value) > 1e-10) { + editValue = ssprintf("%.*f", digits, value); + digits++; + } + editPlaceholder = "10.000000"; + break; } - hStyle hs = c->disp.style; - if(hs.v == 0) hs.v = Style::CONSTRAINT; - ShowGraphicsEditControl((int)p2.x, (int)p2.y, - ssglStrFontSize(Style::TextHeight(hs)) * scale, - editMinWidthChar, editValue); + } + + double width, height; + window->GetContentSize(&width, &height); + hStyle hs = c->disp.style; + if(hs.v == 0) hs.v = Style::CONSTRAINT; + double capHeight = Style::TextHeight(hs); + double fontHeight = VectorFont::Builtin()->GetHeight(capHeight); + double editMinWidth = VectorFont::Builtin()->GetWidth(capHeight, editPlaceholder); + window->ShowEditor(p2.x + width / 2, height / 2 - p2.y, + fontHeight, editMinWidth, + /*isMonospace=*/false, editValue); +} + +void GraphicsWindow::MouseLeftDoubleClick(double mx, double my) { + if(window->IsEditorVisible()) return; + SS.TW.HideEditControl(); + + if(hover.constraint.v) { + EditConstraint(hover.constraint); } } -void GraphicsWindow::EditControlDone(const char *s) { - HideGraphicsEditControl(); +void GraphicsWindow::EditControlDone(const std::string &s) { + window->HideEditor(); + window->Invalidate(); + Constraint *c = SK.GetConstraint(constraintBeingEdited); - if(c->type == Constraint::COMMENT) { + if(c->type == Constraint::Type::COMMENT) { SS.UndoRemember(); c->comment = s; return; } - Expr *e = Expr::From(s, true); - if(e) { + if(Expr *e = Expr::From(s, true)) { SS.UndoRemember(); switch(c->type) { - case Constraint::PROJ_PT_DISTANCE: - case Constraint::PT_LINE_DISTANCE: - case Constraint::PT_FACE_DISTANCE: - case Constraint::PT_PLANE_DISTANCE: - case Constraint::LENGTH_DIFFERENCE: { + case Constraint::Type::PROJ_PT_DISTANCE: + case Constraint::Type::PT_LINE_DISTANCE: + case Constraint::Type::PT_FACE_DISTANCE: + case Constraint::Type::PT_PLANE_DISTANCE: + case Constraint::Type::LENGTH_DIFFERENCE: { // The sign is not displayed to the user, but this is a signed // distance internally. To flip the sign, the user enters a // negative distance. @@ -1364,14 +1447,14 @@ void GraphicsWindow::EditControlDone(const char *s) { } break; } - case Constraint::ANGLE: - case Constraint::LENGTH_RATIO: + case Constraint::Type::ANGLE: + case Constraint::Type::LENGTH_RATIO: // These don't get the units conversion for distance, and // they're always positive c->valA = fabs(e->Eval()); break; - case Constraint::DIAMETER: + case Constraint::Type::DIAMETER: c->valA = fabs(SS.ExprToMm(e)); // If displayed and edited as radius, convert back @@ -1386,18 +1469,7 @@ void GraphicsWindow::EditControlDone(const char *s) { break; } SS.MarkGroupDirty(c->group); - SS.GenerateAll(); - } -} - -bool GraphicsWindow::KeyDown(int c) { - if(c == '\b') { - // Treat backspace identically to escape. - MenuEdit(MNU_UNSELECT_ALL); - return true; } - - return false; } void GraphicsWindow::MouseScroll(double x, double y, int delta) { @@ -1419,36 +1491,38 @@ void GraphicsWindow::MouseScroll(double x, double y, int delta) { offset = offset.Plus(projRight.ScaledBy(rightf - righti)); offset = offset.Plus(projUp.ScaledBy(upf - upi)); - if(SS.TW.shown.screen == TextWindow::SCREEN_EDIT_VIEW) { + if(SS.TW.shown.screen == TextWindow::Screen::EDIT_VIEW) { if(havePainted) { SS.ScheduleShowTW(); } } havePainted = false; - InvalidateGraphics(); + Invalidate(); } -void GraphicsWindow::MouseLeave(void) { +void GraphicsWindow::MouseLeave() { // Un-hover everything when the mouse leaves our window, unless there's // currently a context menu shown. if(!context.active) { hover.Clear(); - toolbarTooltipped = 0; - toolbarHovered = 0; - PaintGraphics(); + toolbarHovered = Command::NONE; + Invalidate(); } SS.extraLine.draw = false; } -void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz, - double rx, double ry, double rz, - bool shiftDown) -{ +void GraphicsWindow::SixDofEvent(Platform::SixDofEvent event) { + if(event.type == Platform::SixDofEvent::Type::RELEASE) { + ZoomToFit(/*includingInvisibles=*/false, /*useSelection=*/true); + Invalidate(); + return; + } + if(!havePainted) return; Vector out = projRight.Cross(projUp); // rotation vector is axis of rotation, and its magnitude is angle - Vector aa = Vector::From(rx, ry, rz); + Vector aa = Vector::From(event.rotationX, event.rotationY, event.rotationZ); // but it's given with respect to screen projection frame aa = aa.ScaleOutOfCsys(projRight, projUp, out); double aam = aa.Magnitude(); @@ -1461,39 +1535,38 @@ void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz, if(gs.points == 1 && gs.n == 1) e = SK.GetEntity(gs.point [0]); if(gs.entities == 1 && gs.n == 1) e = SK.GetEntity(gs.entity[0]); if(e) g = SK.GetGroup(e->group); - if(g && g->type == Group::LINKED && !shiftDown) { + if(g && g->type == Group::Type::LINKED && !event.shiftDown) { // Apply the transformation to a linked part. Gain down the Z // axis, since it's hard to see what you're doing on that one since // it's normal to the screen. - Vector t = projRight.ScaledBy(tx/scale).Plus( - projUp .ScaledBy(ty/scale).Plus( - out .ScaledBy(0.1*tz/scale))); + Vector t = projRight.ScaledBy(event.translationX/scale).Plus( + projUp .ScaledBy(event.translationY/scale).Plus( + out .ScaledBy(0.1*event.translationZ/scale))); Quaternion q = Quaternion::From(aa, aam); // If we go five seconds without SpaceNavigator input, or if we've // switched groups, then consider that a new action and save an undo // point. int64_t now = GetMilliseconds(); - if(now - lastSpaceNavigatorTime > 5000 || - lastSpaceNavigatorGroup.v != g->h.v) + if(now - last6DofTime > 5000 || + last6DofGroup != g->h) { SS.UndoRemember(); } g->TransformImportedBy(t, q); - lastSpaceNavigatorTime = now; - lastSpaceNavigatorGroup = g->h; + last6DofTime = now; + last6DofGroup = g->h; SS.MarkGroupDirty(g->h); - SS.ScheduleGenerateAll(); } else { // Apply the transformation to the view of the everything. The // x and y components are translation; but z component is scale, // not translation, or else it would do nothing in a parallel // projection - offset = offset.Plus(projRight.ScaledBy(tx/scale)); - offset = offset.Plus(projUp.ScaledBy(ty/scale)); - scale *= exp(0.001*tz); + offset = offset.Plus(projRight.ScaledBy(event.translationX/scale)); + offset = offset.Plus(projUp.ScaledBy(event.translationY/scale)); + scale *= exp(0.001*event.translationZ); if(aam > 0.0) { projRight = projRight.RotatedAbout(aa, -aam); @@ -1503,11 +1576,5 @@ void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz, } havePainted = false; - InvalidateGraphics(); -} - -void GraphicsWindow::SpaceNavigatorButtonUp(void) { - ZoomToFit(/*includingInvisibles=*/false, /*useSelection=*/true); - InvalidateGraphics(); + Invalidate(); } - diff --git a/src/platform/entrycli.cpp b/src/platform/entrycli.cpp new file mode 100644 index 0000000..61b20db --- /dev/null +++ b/src/platform/entrycli.cpp @@ -0,0 +1,387 @@ +//----------------------------------------------------------------------------- +// Our main() function for the command-line interface. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" + +static void ShowUsage(const std::string &cmd) { + fprintf(stderr, "Usage: %s [filename...]", cmd.c_str()); +//-----------------------------------------------------------------------------> 80 col */ + fprintf(stderr, R"( + When run, performs an action specified by on every . + +Common options: + -o, --output + For an input file .slvs, replaces the '%%' symbol in + with and uses it as output file. For example, when using + --output %%-2d.png for input files f/a.slvs and f/b.slvs, output files + f/a-2d.png and f/b-2d.png will be written. + -v, --view + Selects the camera direction. can be one of "top", "bottom", + "left", "right", "front", "back", or "isometric". + -t, --chord-tol + Selects the chord tolerance, used for converting exact curves to + piecewise linear, and exact surfaces into triangle meshes. + For export commands, the unit is mm, and the default is 1.0 mm. + For non-export commands, the unit is %%, and the default is 1.0 %%. + -b, --bg-color + Whether to export the background colour in vector formats. Defaults to off. + +Commands: + thumbnail --output --size --view + [--chord-tol ] + Outputs a rendered view of the sketch, like the SolveSpace GUI would. + is x, in pixels. Graphics acceleration is + not used, and the output may look slightly different from the GUI. + export-view --output --view [--chord-tol ] + [--bg-color ] + Exports a view of the sketch, in a 2d vector format. + export-wireframe --output [--chord-tol ] + Exports a wireframe of the sketch, in a 3d vector format. + export-mesh --output [--chord-tol ] + Exports a triangle mesh of solids in the sketch, with exact surfaces + being triangulated first. + export-surfaces --output + Exports exact surfaces of solids in the sketch, if any. + regenerate [--chord-tol ] + Reloads all imported files, regenerates the sketch, and saves it. + Note that, although this is not an export command, it uses absolute + chord tolerance, and can be used to prepare assemblies for export. +)"); + + auto FormatListFromFileFilters = [](const std::vector &filters) { + std::string descr; + for(auto filter : filters) { + descr += "\n "; + descr += filter.name; + descr += " ("; + bool first = true; + for(auto extension : filter.extensions) { + if(!first) { + descr += ", "; + } + descr += extension; + first = false; + } + descr += ")"; + } + return descr; + }; + + fprintf(stderr, R"( +File formats: + thumbnail:%s + export-view:%s + export-wireframe:%s + export-mesh:%s + export-surfaces:%s +)", FormatListFromFileFilters(Platform::RasterFileFilters).c_str(), + FormatListFromFileFilters(Platform::VectorFileFilters).c_str(), + FormatListFromFileFilters(Platform::Vector3dFileFilters).c_str(), + FormatListFromFileFilters(Platform::MeshFileFilters).c_str(), + FormatListFromFileFilters(Platform::SurfaceFileFilters).c_str()); +} + +static bool RunCommand(const std::vector args) { + if(args.size() < 2) return false; + + for(const std::string &arg : args) { + if(arg == "--help" || arg == "-h") { + ShowUsage(args[0]); + return true; + } + } + + std::function runner; + + std::vector inputFiles; + auto ParseInputFile = [&](size_t &argn) { + std::string arg = args[argn]; + if(arg[0] != '-') { + inputFiles.push_back(Platform::Path::From(arg)); + return true; + } else return false; + }; + + std::string outputPattern; + auto ParseOutputPattern = [&](size_t &argn) { + if(argn + 1 < args.size() && (args[argn] == "--output" || + args[argn] == "-o")) { + argn++; + outputPattern = args[argn]; + return true; + } else return false; + }; + + Vector projUp = {}, projRight = {}; + auto ParseViewDirection = [&](size_t &argn) { + if(argn + 1 < args.size() && (args[argn] == "--view" || + args[argn] == "-v")) { + argn++; + if(args[argn] == "top") { + projRight = Vector::From(1, 0, 0); + projUp = Vector::From(0, 0, -1); + } else if(args[argn] == "bottom") { + projRight = Vector::From(1, 0, 0); + projUp = Vector::From(0, 0, 1); + } else if(args[argn] == "left") { + projRight = Vector::From(0, 0, 1); + projUp = Vector::From(0, 1, 0); + } else if(args[argn] == "right") { + projRight = Vector::From(0, 0, -1); + projUp = Vector::From(0, 1, 0); + } else if(args[argn] == "front") { + projRight = Vector::From(1, 0, 0); + projUp = Vector::From(0, 1, 0); + } else if(args[argn] == "back") { + projRight = Vector::From(-1, 0, 0); + projUp = Vector::From(0, 1, 0); + } else if(args[argn] == "isometric") { + projRight = Vector::From(0.707, 0.000, -0.707); + projUp = Vector::From(-0.408, 0.816, -0.408); + } else { + fprintf(stderr, "Unrecognized view direction '%s'\n", args[argn].c_str()); + } + return true; + } else return false; + }; + + double chordTol = 1.0; + auto ParseChordTolerance = [&](size_t &argn) { + if(argn + 1 < args.size() && (args[argn] == "--chord-tol" || + args[argn] == "-t")) { + argn++; + if(sscanf(args[argn].c_str(), "%lf", &chordTol) == 1) { + return true; + } else return false; + } else return false; + }; + + bool bg_color = false; + auto ParseBgColor = [&](size_t &argn) { + if(argn + 1 < args.size() && (args[argn] == "--bg-color" || + args[argn] == "-b")) { + argn++; + if(args[argn] == "on") { + bg_color = true; + return true; + } else if(args[argn] == "off") { + bg_color = false; + return true; + } else return false; + } else return false; + }; + + unsigned width = 0, height = 0; + if(args[1] == "thumbnail") { + auto ParseSize = [&](size_t &argn) { + if(argn + 1 < args.size() && args[argn] == "--size") { + argn++; + if(sscanf(args[argn].c_str(), "%ux%u", &width, &height) == 2) { + return true; + } else return false; + } else return false; + }; + + for(size_t argn = 2; argn < args.size(); argn++) { + if(!(ParseInputFile(argn) || + ParseOutputPattern(argn) || + ParseViewDirection(argn) || + ParseChordTolerance(argn) || + ParseSize(argn))) { + fprintf(stderr, "Unrecognized option '%s'.\n", args[argn].c_str()); + return false; + } + } + + if(width == 0 || height == 0) { + fprintf(stderr, "Non-zero viewport size must be specified.\n"); + return false; + } + + if(EXACT(projUp.Magnitude() == 0 || projRight.Magnitude() == 0)) { + fprintf(stderr, "View direction must be specified.\n"); + return false; + } + + runner = [&](const Platform::Path &output) { + Camera camera = {}; + camera.pixelRatio = 1; + camera.gridFit = true; + camera.width = width; + camera.height = height; + camera.projUp = projUp; + camera.projRight = projRight; + + SS.GW.projUp = projUp; + SS.GW.projRight = projRight; + SS.GW.scale = SS.GW.ZoomToFit(camera); + camera.scale = SS.GW.scale; + camera.offset = SS.GW.offset; + SS.GenerateAll(); + + CairoPixmapRenderer pixmapCanvas; + pixmapCanvas.antialias = true; + pixmapCanvas.SetLighting(SS.GW.GetLighting()); + pixmapCanvas.SetCamera(camera); + pixmapCanvas.Init(); + + pixmapCanvas.StartFrame(); + SS.GW.Draw(&pixmapCanvas); + pixmapCanvas.FlushFrame(); + pixmapCanvas.FinishFrame(); + pixmapCanvas.ReadFrame()->WritePng(output, /*flip=*/true); + + pixmapCanvas.Clear(); + }; + } else if(args[1] == "export-view") { + for(size_t argn = 2; argn < args.size(); argn++) { + if(!(ParseInputFile(argn) || + ParseOutputPattern(argn) || + ParseViewDirection(argn) || + ParseChordTolerance(argn) || + ParseBgColor(argn))) { + fprintf(stderr, "Unrecognized option '%s'.\n", args[argn].c_str()); + return false; + } + } + + if(EXACT(projUp.Magnitude() == 0 || projRight.Magnitude() == 0)) { + fprintf(stderr, "View direction must be specified.\n"); + return false; + } + + runner = [&](const Platform::Path &output) { + SS.GW.projRight = projRight; + SS.GW.projUp = projUp; + SS.exportChordTol = chordTol; + SS.exportBackgroundColor = bg_color; + + SS.ExportViewOrWireframeTo(output, /*exportWireframe=*/false); + }; + } else if(args[1] == "export-wireframe") { + for(size_t argn = 2; argn < args.size(); argn++) { + if(!(ParseInputFile(argn) || + ParseOutputPattern(argn) || + ParseChordTolerance(argn))) { + fprintf(stderr, "Unrecognized option '%s'.\n", args[argn].c_str()); + return false; + } + } + + runner = [&](const Platform::Path &output) { + SS.exportChordTol = chordTol; + + SS.ExportViewOrWireframeTo(output, /*exportWireframe=*/true); + }; + } else if(args[1] == "export-mesh") { + for(size_t argn = 2; argn < args.size(); argn++) { + if(!(ParseInputFile(argn) || + ParseOutputPattern(argn) || + ParseChordTolerance(argn))) { + fprintf(stderr, "Unrecognized option '%s'.\n", args[argn].c_str()); + return false; + } + } + + runner = [&](const Platform::Path &output) { + SS.exportChordTol = chordTol; + + SS.ExportMeshTo(output); + }; + } else if(args[1] == "export-surfaces") { + for(size_t argn = 2; argn < args.size(); argn++) { + if(!(ParseInputFile(argn) || + ParseOutputPattern(argn))) { + fprintf(stderr, "Unrecognized option '%s'.\n", args[argn].c_str()); + return false; + } + } + + runner = [&](const Platform::Path &output) { + StepFileWriter sfw = {}; + sfw.ExportSurfacesTo(output); + }; + } else if(args[1] == "regenerate") { + for(size_t argn = 2; argn < args.size(); argn++) { + if(!(ParseInputFile(argn) || + ParseChordTolerance(argn))) { + fprintf(stderr, "Unrecognized option '%s'.\n", args[argn].c_str()); + return false; + } + } + + outputPattern = "%.slvs"; + + runner = [&](const Platform::Path &output) { + SS.exportChordTol = chordTol; + SS.exportMode = true; + + SS.SaveToFile(output); + }; + } else { + fprintf(stderr, "Unrecognized command '%s'.\n", args[1].c_str()); + return false; + } + + if(outputPattern.empty()) { + fprintf(stderr, "An output pattern must be specified.\n"); + return false; + } else if(outputPattern.find('%') == std::string::npos && inputFiles.size() > 1) { + fprintf(stderr, + "Output pattern must include a %% symbol when using multiple inputs!\n"); + return false; + } + + if(inputFiles.empty()) { + fprintf(stderr, "At least one input file must be specified.\n"); + return false; + } + + for(const Platform::Path &inputFile : inputFiles) { + Platform::Path absInputFile = inputFile.Expand(/*fromCurrentDirectory=*/true); + + Platform::Path outputFile = Platform::Path::From(outputPattern); + size_t replaceAt = outputFile.raw.find('%'); + if(replaceAt != std::string::npos) { + Platform::Path outputSubst = inputFile.Parent(); + if(outputSubst.IsEmpty()) { + outputSubst = Platform::Path::From(inputFile.FileStem()); + } else { + outputSubst = outputSubst.Join(inputFile.FileStem()); + } + outputFile.raw.replace(replaceAt, 1, outputSubst.raw); + } + Platform::Path absOutputFile = outputFile.Expand(/*fromCurrentDirectory=*/true); + + SS.Init(); + if(!SS.LoadFromFile(absInputFile)) { + fprintf(stderr, "Cannot load '%s'!\n", inputFile.raw.c_str()); + return false; + } + SS.AfterNewFile(); + runner(absOutputFile); + SK.Clear(); + SS.Clear(); + + fprintf(stderr, "Written '%s'.\n", outputFile.raw.c_str()); + } + + return true; +} + +int main(int argc, char **argv) { + std::vector args = Platform::InitCli(argc, argv); + + if(args.size() == 1) { + ShowUsage(args[0]); + return 0; + } + + if(!RunCommand(args)) { + return 1; + } else { + return 0; + } +} diff --git a/src/platform/entrygui.cpp b/src/platform/entrygui.cpp new file mode 100644 index 0000000..397831a --- /dev/null +++ b/src/platform/entrygui.cpp @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// Our main() function for the graphical interface. +// +// Copyright 2018 +//----------------------------------------------------------------------------- +#include "solvespace.h" +#if defined(WIN32) +# include +#endif + +using namespace SolveSpace; + +int main(int argc, char** argv) { + std::vector args = Platform::InitGui(argc, argv); + + Platform::Open3DConnexion(); + SS.Init(); + + if(args.size() >= 2) { + if(args.size() > 2) { + dbp("Only the first file passed on command line will be opened."); + } + + SS.Load(Platform::Path::From(args.back())); + } + + Platform::RunGui(); + + Platform::Close3DConnexion(); + SS.Clear(); + SK.Clear(); + Platform::ClearGui(); + + return 0; +} + +#if defined(WIN32) +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, INT nCmdShow) { + return main(0, NULL); +} +#endif diff --git a/src/platform/gui.cpp b/src/platform/gui.cpp new file mode 100644 index 0000000..ff9b2cf --- /dev/null +++ b/src/platform/gui.cpp @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------------- +// Platform-dependent GUI functionality that has only minor differences. +// +// Copyright 2018 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" + +namespace SolveSpace { +namespace Platform { + +//----------------------------------------------------------------------------- +// Keyboard events +//----------------------------------------------------------------------------- + +std::string AcceleratorDescription(const KeyboardEvent &accel) { + std::string label; + if(accel.controlDown) { +#ifdef __APPLE__ + label += "⌘+"; +#else + label += "Ctrl+"; +#endif + } + + if(accel.shiftDown) { + label += "Shift+"; + } + + switch(accel.key) { + case KeyboardEvent::Key::FUNCTION: + label += ssprintf("F%d", accel.num); + break; + + case KeyboardEvent::Key::CHARACTER: + if(accel.chr == '\t') { + label += "Tab"; + } else if(accel.chr == ' ') { + label += "Space"; + } else if(accel.chr == '\x1b') { + label += "Esc"; + } else if(accel.chr == '\x7f') { + label += "Del"; + } else if(accel.chr != 0) { + label += toupper((char)(accel.chr & 0xff)); + } + break; + } + + return label; +} + +//----------------------------------------------------------------------------- +// Settings +//----------------------------------------------------------------------------- + +void Settings::FreezeBool(const std::string &key, bool value) { + FreezeInt(key, (int)value); +} + +bool Settings::ThawBool(const std::string &key, bool defaultValue) { + return ThawInt(key, (int)defaultValue) != 0; +} + +void Settings::FreezeColor(const std::string &key, RgbaColor value) { + FreezeInt(key, value.ToPackedInt()); +} + +RgbaColor Settings::ThawColor(const std::string &key, RgbaColor defaultValue) { + return RgbaColor::FromPackedInt(ThawInt(key, defaultValue.ToPackedInt())); +} + +//----------------------------------------------------------------------------- +// File dialogs +//----------------------------------------------------------------------------- + +void FileDialog::AddFilter(const FileFilter &filter) { + AddFilter(Translate("file-type", filter.name.c_str()), filter.extensions); +} + +void FileDialog::AddFilters(const std::vector &filters) { + for(auto filter : filters) AddFilter(filter); +} + +std::vector SolveSpaceModelFileFilters = { + { CN_("file-type", "SolveSpace models"), { "slvs" } }, +}; + +std::vector SolveSpaceLinkFileFilters = { + { CN_("file-type", "SolveSpace models"), { "slvs" } }, + { CN_("file-type", "IDF circuit board"), { "emn" } }, +}; + +std::vector RasterFileFilters = { + { CN_("file-type", "PNG image"), { "png" } }, +}; + +std::vector MeshFileFilters = { + { CN_("file-type", "STL mesh"), { "stl" } }, + { CN_("file-type", "Wavefront OBJ mesh"), { "obj" } }, + { CN_("file-type", "Three.js-compatible mesh, with viewer"), { "html" } }, + { CN_("file-type", "Three.js-compatible mesh, mesh only"), { "js" } }, + { CN_("file-type", "Q3D Object file"), { "q3do" } }, + { CN_("file-type", "VRML text file"), { "wrl" } }, +}; + +std::vector SurfaceFileFilters = { + { CN_("file-type", "STEP file"), { "step", "stp" } }, +}; + +std::vector VectorFileFilters = { + { CN_("file-type", "PDF file"), { "pdf" } }, + { CN_("file-type", "Encapsulated PostScript"), { "eps", "ps" } }, + { CN_("file-type", "Scalable Vector Graphics"), { "svg" } }, + { CN_("file-type", "STEP file"), { "step", "stp" } }, + { CN_("file-type", "DXF file (AutoCAD 2007)"), { "dxf" } }, + { CN_("file-type", "HPGL file"), { "plt", "hpgl" } }, + { CN_("file-type", "G Code"), { "ngc", "txt" } }, +}; + +std::vector Vector3dFileFilters = { + { CN_("file-type", "STEP file"), { "step", "stp" } }, + { CN_("file-type", "DXF file (AutoCAD 2007)"), { "dxf" } }, +}; + +std::vector ImportFileFilters = { + { CN_("file-type", "AutoCAD DXF and DWG files"), { "dxf", "dwg" } }, +}; + +std::vector CsvFileFilters = { + { CN_("file-type", "Comma-separated values"), { "csv" } }, +}; + + +} +} diff --git a/src/platform/gui.h b/src/platform/gui.h new file mode 100644 index 0000000..7b2cdf5 --- /dev/null +++ b/src/platform/gui.h @@ -0,0 +1,389 @@ +//----------------------------------------------------------------------------- +// An abstraction for platform-dependent GUI functionality. +// +// Copyright 2018 whitequark +//----------------------------------------------------------------------------- + +#ifndef SOLVESPACE_GUI_H +#define SOLVESPACE_GUI_H + +class RgbaColor; + +namespace Platform { + +//----------------------------------------------------------------------------- +// Events +//----------------------------------------------------------------------------- + +// A mouse input event. +class MouseEvent { +public: + enum class Type { + MOTION, + PRESS, + DBL_PRESS, + RELEASE, + SCROLL_VERT, + LEAVE, + }; + + enum class Button { + NONE, + LEFT, + MIDDLE, + RIGHT, + }; + + Type type; + double x; + double y; + bool shiftDown; + bool controlDown; + union { + Button button; // for Type::{MOTION,PRESS,DBL_PRESS,RELEASE} + double scrollDelta; // for Type::SCROLL_VERT + }; +}; + +// A 3-DOF input event. +struct SixDofEvent { + enum class Type { + MOTION, + PRESS, + RELEASE, + }; + + enum class Button { + FIT, + }; + + Type type; + bool shiftDown; + bool controlDown; + double translationX, translationY, translationZ; // for Type::MOTION + double rotationX, rotationY, rotationZ; // for Type::MOTION + Button button; // for Type::{PRESS,RELEASE} +}; + +// A keyboard input event. +struct KeyboardEvent { + enum class Type { + PRESS, + RELEASE, + }; + + enum class Key { + CHARACTER, + FUNCTION, + }; + + Type type; + Key key; + union { + char32_t chr; // for Key::CHARACTER + int num; // for Key::FUNCTION + }; + bool shiftDown; + bool controlDown; + + bool Equals(const KeyboardEvent &other) { + return type == other.type && key == other.key && + shiftDown == other.shiftDown && controlDown == other.controlDown && + ((key == Key::CHARACTER && chr == other.chr) || + (key == Key::FUNCTION && num == other.num)); + } +}; + +std::string AcceleratorDescription(const KeyboardEvent &accel); + +//----------------------------------------------------------------------------- +// Interfaces +//----------------------------------------------------------------------------- + +// Handling fatal errors. +[[noreturn]] +void FatalError(const std::string &message); + +// A native settings store. +class Settings { +public: + virtual ~Settings() = default; + + virtual void FreezeInt(const std::string &key, uint32_t value) = 0; + virtual uint32_t ThawInt(const std::string &key, uint32_t defaultValue = 0) = 0; + + virtual void FreezeFloat(const std::string &key, double value) = 0; + virtual double ThawFloat(const std::string &key, double defaultValue = 0.0) = 0; + + virtual void FreezeString(const std::string &key, const std::string &value) = 0; + virtual std::string ThawString(const std::string &key, + const std::string &defaultValue = "") = 0; + + virtual void FreezeBool(const std::string &key, bool value); + virtual bool ThawBool(const std::string &key, bool defaultValue = false); + + virtual void FreezeColor(const std::string &key, RgbaColor value); + virtual RgbaColor ThawColor(const std::string &key, RgbaColor defaultValue); +}; + +typedef std::shared_ptr SettingsRef; + +SettingsRef GetSettings(); + +// A native single-shot timer. +class Timer { +public: + std::function onTimeout; + + virtual ~Timer() = default; + + virtual void RunAfter(unsigned milliseconds) = 0; + virtual void RunAfterNextFrame() { RunAfter(1); } + virtual void RunAfterProcessingEvents() { RunAfter(0); } +}; + +typedef std::shared_ptr TimerRef; + +TimerRef CreateTimer(); + +// A native menu item. +class MenuItem { +public: + enum class Indicator { + NONE, + CHECK_MARK, + RADIO_MARK, + }; + + std::function onTrigger; + + virtual ~MenuItem() = default; + + virtual void SetAccelerator(KeyboardEvent accel) = 0; + virtual void SetIndicator(Indicator type) = 0; + virtual void SetEnabled(bool enabled) = 0; + virtual void SetActive(bool active) = 0; +}; + +typedef std::shared_ptr MenuItemRef; + +// A native menu. +class Menu { +public: + virtual ~Menu() = default; + + virtual std::shared_ptr AddItem( + const std::string &label, std::function onTrigger = std::function(), + bool mnemonics = true) = 0; + virtual std::shared_ptr

AddSubMenu(const std::string &label) = 0; + virtual void AddSeparator() = 0; + + virtual void PopUp() = 0; + + virtual void Clear() = 0; +}; + +typedef std::shared_ptr MenuRef; + +// A native menu bar. +class MenuBar { +public: + virtual ~MenuBar() = default; + + virtual std::shared_ptr AddSubMenu(const std::string &label) = 0; + + virtual void Clear() = 0; +}; + +typedef std::shared_ptr MenuBarRef; + +MenuRef CreateMenu(); +MenuBarRef GetOrCreateMainMenu(bool *unique); + +// A native top-level window, with an OpenGL context, and an editor overlay. +class Window { +public: + enum class Kind { + TOPLEVEL, + TOOL, + }; + + enum class Cursor { + POINTER, + HAND + }; + + std::function onClose; + std::function onFullScreen; + std::function onMouseEvent; + std::function onSixDofEvent; + std::function onKeyboardEvent; + std::function onEditingDone; + std::function onScrollbarAdjusted; + std::function onRender; + + virtual ~Window() = default; + + // Returns physical display DPI. + virtual double GetPixelDensity() = 0; + // Returns raster graphics and coordinate scale (already applied on the platform side), + // i.e. size of logical pixel in physical pixels, or device pixel ratio. + virtual int GetDevicePixelRatio() = 0; + // Returns (fractional) font scale, to be applied on top of (integral) device pixel ratio. + virtual double GetDeviceFontScale() { + return GetPixelDensity() / GetDevicePixelRatio() / 96.0; + } + + virtual bool IsVisible() = 0; + virtual void SetVisible(bool visible) = 0; + virtual void Focus() = 0; + + virtual bool IsFullScreen() = 0; + virtual void SetFullScreen(bool fullScreen) = 0; + + virtual void SetTitle(const std::string &title) = 0; + virtual bool SetTitleForFilename(const Path &filename) { return false; } + + virtual void SetMenuBar(MenuBarRef menuBar) = 0; + + virtual void GetContentSize(double *width, double *height) = 0; + virtual void SetMinContentSize(double width, double height) = 0; + + virtual void FreezePosition(SettingsRef settings, const std::string &key) = 0; + virtual void ThawPosition(SettingsRef settings, const std::string &key) = 0; + + virtual void SetCursor(Cursor cursor) = 0; + virtual void SetTooltip(const std::string &text, double x, double y, + double width, double height) = 0; + + virtual bool IsEditorVisible() = 0; + virtual void ShowEditor(double x, double y, double fontHeight, double minWidth, + bool isMonospace, const std::string &text) = 0; + virtual void HideEditor() = 0; + + virtual void SetScrollbarVisible(bool visible) = 0; + virtual void ConfigureScrollbar(double min, double max, double pageSize) = 0; + virtual double GetScrollbarPosition() = 0; + virtual void SetScrollbarPosition(double pos) = 0; + + virtual void Invalidate() = 0; +}; + +typedef std::shared_ptr WindowRef; + +WindowRef CreateWindow(Window::Kind kind = Window::Kind::TOPLEVEL, + WindowRef parentWindow = NULL); + +// 3DConnexion support. +void Open3DConnexion(); +void Close3DConnexion(); +void Request3DConnexionEventsForWindow(WindowRef window); + +// A native dialog that asks for one choice out of several. +class MessageDialog { +public: + enum class Type { + INFORMATION, + QUESTION, + WARNING, + ERROR + }; + + enum class Response { + NONE, + OK, + YES, + NO, + CANCEL + }; + + std::function onResponse; + + virtual ~MessageDialog() = default; + + virtual void SetType(Type type) = 0; + virtual void SetTitle(std::string title) = 0; + virtual void SetMessage(std::string message) = 0; + virtual void SetDescription(std::string description) = 0; + + virtual void AddButton(std::string label, Response response, bool isDefault = false) = 0; + + virtual Response RunModal() = 0; + virtual void ShowModal() { + Response response = RunModal(); + if(onResponse) { + onResponse(response); + } + } +}; + +typedef std::shared_ptr MessageDialogRef; + +MessageDialogRef CreateMessageDialog(WindowRef parentWindow); + +// A file filter. +struct FileFilter { + std::string name; + std::vector extensions; +}; + +// SolveSpace's native file format +extern std::vector SolveSpaceModelFileFilters; +// SolveSpace's linkable file formats +extern std::vector SolveSpaceLinkFileFilters; +// Raster image +extern std::vector RasterFileFilters; +// Triangle mesh +extern std::vector MeshFileFilters; +// NURBS surfaces +extern std::vector SurfaceFileFilters; +// 2d vector (lines and curves) format +extern std::vector VectorFileFilters; +// 3d vector (wireframe lines and curves) format +extern std::vector Vector3dFileFilters; +// Any importable format +extern std::vector ImportFileFilters; +// Comma-separated value, like a spreadsheet would use +extern std::vector CsvFileFilters; + +// A native dialog that asks to choose a file. +class FileDialog { +public: + virtual ~FileDialog() = default; + + virtual void SetTitle(std::string title) = 0; + virtual void SetCurrentName(std::string name) = 0; + + virtual Platform::Path GetFilename() = 0; + virtual void SetFilename(Platform::Path path) = 0; + + virtual void AddFilter(std::string name, std::vector extensions) = 0; + void AddFilter(const FileFilter &filter); + void AddFilters(const std::vector &filters); + + virtual void FreezeChoices(SettingsRef settings, const std::string &key) = 0; + virtual void ThawChoices(SettingsRef settings, const std::string &key) = 0; + + virtual bool RunModal() = 0; +}; + +typedef std::shared_ptr FileDialogRef; + +FileDialogRef CreateOpenFileDialog(WindowRef parentWindow); +FileDialogRef CreateSaveFileDialog(WindowRef parentWindow); + +//----------------------------------------------------------------------------- +// Application-wide APIs +//----------------------------------------------------------------------------- + +std::vector GetFontFiles(); +void OpenInBrowser(const std::string &url); + +std::vector InitGui(int argc, char **argv); +void RunGui(); +void ExitGui(); +void ClearGui(); + +} + +#endif diff --git a/src/platform/guigtk.cpp b/src/platform/guigtk.cpp new file mode 100644 index 0000000..cd5d0b8 --- /dev/null +++ b/src/platform/guigtk.cpp @@ -0,0 +1,1493 @@ +//----------------------------------------------------------------------------- +// The GTK-based implementation of platform-dependent GUI functionality. +// +// Copyright 2018 whitequark +//----------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#if defined(HAVE_GTK_FILECHOOSERNATIVE) +# include +#endif + +#if defined(HAVE_SPACEWARE) +# include +# include +#endif + +#include "solvespace.h" + +namespace SolveSpace { +namespace Platform { + +//----------------------------------------------------------------------------- +// Utility functions +//----------------------------------------------------------------------------- + +static std::string PrepareMnemonics(std::string label) { + std::replace(label.begin(), label.end(), '&', '_'); + return label; +} + +static std::string PrepareTitle(const std::string &title) { + return title + " — SolveSpace"; +} + +//----------------------------------------------------------------------------- +// Fatal errors +//----------------------------------------------------------------------------- + +void FatalError(const std::string &message) { + fprintf(stderr, "%s", message.c_str()); + abort(); +} + +//----------------------------------------------------------------------------- +// Settings +//----------------------------------------------------------------------------- + +class SettingsImplGtk final : public Settings { +public: + // Why aren't we using GSettings? Two reasons. It doesn't allow to easily see whether + // the setting had the default value, and it requires to install a schema globally. + Path _path; + json_object *_json = NULL; + + static Path GetConfigPath() { + Path configHome; + if(getenv("XDG_CONFIG_HOME")) { + configHome = Path::From(getenv("XDG_CONFIG_HOME")); + } else if(getenv("HOME")) { + configHome = Path::From(getenv("HOME")).Join(".config"); + } else { + dbp("neither XDG_CONFIG_HOME nor HOME are set"); + return Path::From(""); + } + if(!configHome.IsEmpty()) { + configHome = configHome.Join("solvespace"); + } + + const char *configHomeC = configHome.raw.c_str(); + struct stat st; + if(stat(configHomeC, &st)) { + if(errno == ENOENT) { + if(mkdir(configHomeC, 0777)) { + dbp("cannot mkdir %s: %s", configHomeC, strerror(errno)); + return Path::From(""); + } + } else { + dbp("cannot stat %s: %s", configHomeC, strerror(errno)); + return Path::From(""); + } + } else if(!S_ISDIR(st.st_mode)) { + dbp("%s is not a directory", configHomeC); + return Path::From(""); + } + + return configHome.Join("settings.json"); + } + + SettingsImplGtk() { + _path = GetConfigPath(); + if(_path.IsEmpty()) { + dbp("settings will not be saved"); + } else { + _json = json_object_from_file(_path.raw.c_str()); + if(!_json && errno != ENOENT) { + dbp("cannot load settings: %s", strerror(errno)); + } + } + + if(_json == NULL) { + _json = json_object_new_object(); + } + } + + ~SettingsImplGtk() override { + if(!_path.IsEmpty()) { + // json-c <0.12 has the first argument non-const + if(json_object_to_file_ext((char *)_path.raw.c_str(), _json, + JSON_C_TO_STRING_PRETTY)) { + dbp("cannot save settings: %s", strerror(errno)); + } + } + + json_object_put(_json); + } + + void FreezeInt(const std::string &key, uint32_t value) override { + struct json_object *jsonValue = json_object_new_int(value); + json_object_object_add(_json, key.c_str(), jsonValue); + } + + uint32_t ThawInt(const std::string &key, uint32_t defaultValue) override { + struct json_object *jsonValue; + if(json_object_object_get_ex(_json, key.c_str(), &jsonValue)) { + return json_object_get_int(jsonValue); + } + return defaultValue; + } + + void FreezeBool(const std::string &key, bool value) override { + struct json_object *jsonValue = json_object_new_boolean(value); + json_object_object_add(_json, key.c_str(), jsonValue); + } + + bool ThawBool(const std::string &key, bool defaultValue) override { + struct json_object *jsonValue; + if(json_object_object_get_ex(_json, key.c_str(), &jsonValue)) { + return json_object_get_boolean(jsonValue); + } + return defaultValue; + } + + void FreezeFloat(const std::string &key, double value) override { + struct json_object *jsonValue = json_object_new_double(value); + json_object_object_add(_json, key.c_str(), jsonValue); + } + + double ThawFloat(const std::string &key, double defaultValue) override { + struct json_object *jsonValue; + if(json_object_object_get_ex(_json, key.c_str(), &jsonValue)) { + return json_object_get_double(jsonValue); + } + return defaultValue; + } + + void FreezeString(const std::string &key, const std::string &value) override { + struct json_object *jsonValue = json_object_new_string(value.c_str()); + json_object_object_add(_json, key.c_str(), jsonValue); + } + + std::string ThawString(const std::string &key, + const std::string &defaultValue = "") override { + struct json_object *jsonValue; + if(json_object_object_get_ex(_json, key.c_str(), &jsonValue)) { + return json_object_get_string(jsonValue); + } + return defaultValue; + } +}; + +SettingsRef GetSettings() { + static std::shared_ptr settings; + if(!settings) { + settings = std::make_shared(); + } + return settings; +} + +//----------------------------------------------------------------------------- +// Timers +//----------------------------------------------------------------------------- + +class TimerImplGtk final : public Timer { +public: + sigc::connection _connection; + + void RunAfter(unsigned milliseconds) override { + if(!_connection.empty()) { + _connection.disconnect(); + } + + auto handler = [this]() { + if(this->onTimeout) { + this->onTimeout(); + } + return false; + }; + _connection = Glib::signal_timeout().connect(handler, milliseconds); + } +}; + +TimerRef CreateTimer() { + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// GTK menu extensions +//----------------------------------------------------------------------------- + +class GtkMenuItem : public Gtk::CheckMenuItem { + Platform::MenuItem *_receiver; + bool _has_indicator; + bool _synthetic_event; + +public: + GtkMenuItem(Platform::MenuItem *receiver) : + _receiver(receiver), _has_indicator(false), _synthetic_event(false) { + } + + void set_accel_key(const Gtk::AccelKey &accel_key) { + Gtk::CheckMenuItem::set_accel_key(accel_key); + } + + bool has_indicator() const { + return _has_indicator; + } + + void set_has_indicator(bool has_indicator) { + _has_indicator = has_indicator; + } + + void set_active(bool active) { + if(Gtk::CheckMenuItem::get_active() == active) + return; + + _synthetic_event = true; + Gtk::CheckMenuItem::set_active(active); + _synthetic_event = false; + } + +protected: + void on_activate() override { + Gtk::CheckMenuItem::on_activate(); + + if(!_synthetic_event && _receiver->onTrigger) { + _receiver->onTrigger(); + } + } + + void draw_indicator_vfunc(const Cairo::RefPtr &cr) override { + if(_has_indicator) { + Gtk::CheckMenuItem::draw_indicator_vfunc(cr); + } + } +}; + +//----------------------------------------------------------------------------- +// Menus +//----------------------------------------------------------------------------- + +class MenuItemImplGtk final : public MenuItem { +public: + GtkMenuItem gtkMenuItem; + + MenuItemImplGtk() : gtkMenuItem(this) {} + + void SetAccelerator(KeyboardEvent accel) override { + guint accelKey = 0; + if(accel.key == KeyboardEvent::Key::CHARACTER) { + if(accel.chr == '\t') { + accelKey = GDK_KEY_Tab; + } else if(accel.chr == '\x1b') { + accelKey = GDK_KEY_Escape; + } else if(accel.chr == '\x7f') { + accelKey = GDK_KEY_Delete; + } else { + accelKey = gdk_unicode_to_keyval(accel.chr); + } + } else if(accel.key == KeyboardEvent::Key::FUNCTION) { + accelKey = GDK_KEY_F1 + accel.num - 1; + } + + Gdk::ModifierType accelMods = {}; + if(accel.shiftDown) { + accelMods |= Gdk::SHIFT_MASK; + } + if(accel.controlDown) { + accelMods |= Gdk::CONTROL_MASK; + } + + gtkMenuItem.set_accel_key(Gtk::AccelKey(accelKey, accelMods)); + } + + void SetIndicator(Indicator type) override { + switch(type) { + case Indicator::NONE: + gtkMenuItem.set_has_indicator(false); + break; + + case Indicator::CHECK_MARK: + gtkMenuItem.set_has_indicator(true); + gtkMenuItem.set_draw_as_radio(false); + break; + + case Indicator::RADIO_MARK: + gtkMenuItem.set_has_indicator(true); + gtkMenuItem.set_draw_as_radio(true); + break; + } + } + + void SetActive(bool active) override { + ssassert(gtkMenuItem.has_indicator(), + "Cannot change state of a menu item without indicator"); + gtkMenuItem.set_active(active); + } + + void SetEnabled(bool enabled) override { + gtkMenuItem.set_sensitive(enabled); + } +}; + +class MenuImplGtk final : public Menu { +public: + Gtk::Menu gtkMenu; + std::vector> menuItems; + std::vector> subMenus; + + MenuItemRef AddItem(const std::string &label, + std::function onTrigger = NULL, + bool mnemonics = true) override { + auto menuItem = std::make_shared(); + menuItems.push_back(menuItem); + + menuItem->gtkMenuItem.set_label(mnemonics ? PrepareMnemonics(label) : label); + menuItem->gtkMenuItem.set_use_underline(mnemonics); + menuItem->gtkMenuItem.show(); + menuItem->onTrigger = onTrigger; + gtkMenu.append(menuItem->gtkMenuItem); + + return menuItem; + } + + MenuRef AddSubMenu(const std::string &label) override { + auto menuItem = std::make_shared(); + menuItems.push_back(menuItem); + + auto subMenu = std::make_shared(); + subMenus.push_back(subMenu); + + menuItem->gtkMenuItem.set_label(PrepareMnemonics(label)); + menuItem->gtkMenuItem.set_use_underline(true); + menuItem->gtkMenuItem.set_submenu(subMenu->gtkMenu); + menuItem->gtkMenuItem.show_all(); + gtkMenu.append(menuItem->gtkMenuItem); + + return subMenu; + } + + void AddSeparator() override { + Gtk::SeparatorMenuItem *gtkMenuItem = Gtk::manage(new Gtk::SeparatorMenuItem()); + gtkMenuItem->show(); + gtkMenu.append(*Gtk::manage(gtkMenuItem)); + } + + void PopUp() override { + Glib::RefPtr loop = Glib::MainLoop::create(); + auto signal = gtkMenu.signal_deactivate().connect([&]() { loop->quit(); }); + + gtkMenu.show_all(); + gtkMenu.popup(0, GDK_CURRENT_TIME); + loop->run(); + signal.disconnect(); + } + + void Clear() override { + gtkMenu.foreach([&](Gtk::Widget &w) { gtkMenu.remove(w); }); + menuItems.clear(); + subMenus.clear(); + } +}; + +MenuRef CreateMenu() { + return std::make_shared(); +} + +class MenuBarImplGtk final : public MenuBar { +public: + Gtk::MenuBar gtkMenuBar; + std::vector> subMenus; + + MenuRef AddSubMenu(const std::string &label) override { + auto subMenu = std::make_shared(); + subMenus.push_back(subMenu); + + Gtk::MenuItem *gtkMenuItem = Gtk::manage(new Gtk::MenuItem); + gtkMenuItem->set_label(PrepareMnemonics(label)); + gtkMenuItem->set_use_underline(true); + gtkMenuItem->set_submenu(subMenu->gtkMenu); + gtkMenuItem->show_all(); + gtkMenuBar.append(*gtkMenuItem); + + return subMenu; + } + + void Clear() override { + gtkMenuBar.foreach([&](Gtk::Widget &w) { gtkMenuBar.remove(w); }); + subMenus.clear(); + } +}; + +MenuBarRef GetOrCreateMainMenu(bool *unique) { + *unique = false; + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// GTK GL and window extensions +//----------------------------------------------------------------------------- + +class GtkGLWidget : public Gtk::GLArea { + Window *_receiver; + +public: + GtkGLWidget(Platform::Window *receiver) : _receiver(receiver) { + set_has_depth_buffer(true); + set_can_focus(true); + set_events(Gdk::POINTER_MOTION_MASK | + Gdk::BUTTON_PRESS_MASK | + Gdk::BUTTON_RELEASE_MASK | + Gdk::BUTTON_MOTION_MASK | + Gdk::SCROLL_MASK | + Gdk::LEAVE_NOTIFY_MASK | + Gdk::KEY_PRESS_MASK | + Gdk::KEY_RELEASE_MASK); + } + +protected: + // Work around a bug fixed in GTKMM 3.22: + // https://mail.gnome.org/archives/gtkmm-list/2016-April/msg00020.html + Glib::RefPtr on_create_context() override { + return get_window()->create_gl_context(); + } + + bool on_render(const Glib::RefPtr &context) override { + if(_receiver->onRender) { + _receiver->onRender(); + } + return true; + } + + bool process_pointer_event(MouseEvent::Type type, double x, double y, + guint state, guint button = 0, int scroll_delta = 0) { + MouseEvent event = {}; + event.type = type; + event.x = x; + event.y = y; + if(button == 1 || (state & GDK_BUTTON1_MASK) != 0) { + event.button = MouseEvent::Button::LEFT; + } else if(button == 2 || (state & GDK_BUTTON2_MASK) != 0) { + event.button = MouseEvent::Button::MIDDLE; + } else if(button == 3 || (state & GDK_BUTTON3_MASK) != 0) { + event.button = MouseEvent::Button::RIGHT; + } + if((state & GDK_SHIFT_MASK) != 0) { + event.shiftDown = true; + } + if((state & GDK_CONTROL_MASK) != 0) { + event.controlDown = true; + } + if(scroll_delta != 0) { + event.scrollDelta = scroll_delta; + } + + if(_receiver->onMouseEvent) { + return _receiver->onMouseEvent(event); + } + + return false; + } + + bool on_motion_notify_event(GdkEventMotion *gdk_event) override { + if(process_pointer_event(MouseEvent::Type::MOTION, + gdk_event->x, gdk_event->y, gdk_event->state)) + return true; + + return Gtk::GLArea::on_motion_notify_event(gdk_event); + } + + bool on_button_press_event(GdkEventButton *gdk_event) override { + MouseEvent::Type type; + if(gdk_event->type == GDK_BUTTON_PRESS) { + type = MouseEvent::Type::PRESS; + } else if(gdk_event->type == GDK_2BUTTON_PRESS) { + type = MouseEvent::Type::DBL_PRESS; + } else { + return Gtk::GLArea::on_button_press_event(gdk_event); + } + + if(process_pointer_event(type, gdk_event->x, gdk_event->y, + gdk_event->state, gdk_event->button)) + return true; + + return Gtk::GLArea::on_button_press_event(gdk_event); + } + + bool on_button_release_event(GdkEventButton *gdk_event) override { + if(process_pointer_event(MouseEvent::Type::RELEASE, + gdk_event->x, gdk_event->y, + gdk_event->state, gdk_event->button)) + return true; + + return Gtk::GLArea::on_button_release_event(gdk_event); + } + + bool on_scroll_event(GdkEventScroll *gdk_event) override { + int delta; + if(gdk_event->delta_y < 0 || gdk_event->direction == GDK_SCROLL_UP) { + delta = 1; + } else if(gdk_event->delta_y > 0 || gdk_event->direction == GDK_SCROLL_DOWN) { + delta = -1; + } else { + return false; + } + + if(process_pointer_event(MouseEvent::Type::SCROLL_VERT, + gdk_event->x, gdk_event->y, + gdk_event->state, 0, delta)) + return true; + + return Gtk::GLArea::on_scroll_event(gdk_event); + } + + bool on_leave_notify_event(GdkEventCrossing *gdk_event) override { + if(process_pointer_event(MouseEvent::Type::LEAVE, + gdk_event->x, gdk_event->y, gdk_event->state)) + return true; + + return Gtk::GLArea::on_leave_notify_event(gdk_event); + } + + bool process_key_event(KeyboardEvent::Type type, GdkEventKey *gdk_event) { + KeyboardEvent event = {}; + event.type = type; + + Gdk::ModifierType mod_mask = get_modifier_mask(Gdk::MODIFIER_INTENT_DEFAULT_MOD_MASK); + if((gdk_event->state & mod_mask) & ~(GDK_SHIFT_MASK|GDK_CONTROL_MASK)) { + return false; + } + + event.shiftDown = (gdk_event->state & GDK_SHIFT_MASK) != 0; + event.controlDown = (gdk_event->state & GDK_CONTROL_MASK) != 0; + + char32_t chr = gdk_keyval_to_unicode(gdk_keyval_to_lower(gdk_event->keyval)); + if(chr != 0) { + event.key = KeyboardEvent::Key::CHARACTER; + event.chr = chr; + } else if(gdk_event->keyval >= GDK_KEY_F1 && + gdk_event->keyval <= GDK_KEY_F12) { + event.key = KeyboardEvent::Key::FUNCTION; + event.num = gdk_event->keyval - GDK_KEY_F1 + 1; + } else { + return false; + } + + if(_receiver->onKeyboardEvent) { + return _receiver->onKeyboardEvent(event); + } + + return false; + } + + bool on_key_press_event(GdkEventKey *gdk_event) override { + if(process_key_event(KeyboardEvent::Type::PRESS, gdk_event)) + return true; + + return Gtk::GLArea::on_key_press_event(gdk_event); + } + + bool on_key_release_event(GdkEventKey *gdk_event) override { + if(process_key_event(KeyboardEvent::Type::RELEASE, gdk_event)) + return true; + + return Gtk::GLArea::on_key_release_event(gdk_event); + } +}; + +class GtkEditorOverlay : public Gtk::Fixed { + Window *_receiver; + GtkGLWidget _gl_widget; + Gtk::Entry _entry; + +public: + GtkEditorOverlay(Platform::Window *receiver) : _receiver(receiver), _gl_widget(receiver) { + add(_gl_widget); + + _entry.set_no_show_all(true); + _entry.set_has_frame(false); + add(_entry); + + _entry.signal_activate(). + connect(sigc::mem_fun(this, &GtkEditorOverlay::on_activate)); + } + + bool is_editing() const { + return _entry.is_visible(); + } + + void start_editing(int x, int y, int font_height, int min_width, bool is_monospace, + const std::string &val) { + Pango::FontDescription font_desc; + font_desc.set_family(is_monospace ? "monospace" : "normal"); + font_desc.set_absolute_size(font_height * Pango::SCALE); + _entry.override_font(font_desc); + + // The y coordinate denotes baseline. + Pango::FontMetrics font_metrics = get_pango_context()->get_metrics(font_desc); + y -= font_metrics.get_ascent() / Pango::SCALE; + + Glib::RefPtr layout = Pango::Layout::create(get_pango_context()); + layout->set_font_description(font_desc); + // Add one extra char width to avoid scrolling. + layout->set_text(val + " "); + int width = layout->get_logical_extents().get_width(); + + Gtk::Border margin = _entry.get_style_context()->get_margin(); + Gtk::Border border = _entry.get_style_context()->get_border(); + Gtk::Border padding = _entry.get_style_context()->get_padding(); + move(_entry, + x - margin.get_left() - border.get_left() - padding.get_left(), + y - margin.get_top() - border.get_top() - padding.get_top()); + + int fitWidth = width / Pango::SCALE + padding.get_left() + padding.get_right(); + _entry.set_size_request(max(fitWidth, min_width), -1); + queue_resize(); + + _entry.set_text(val); + + if(!_entry.is_visible()) { + _entry.show(); + _entry.grab_focus(); + + // We grab the input for ourselves and not the entry to still have + // the pointer events go through the underlay. + add_modal_grab(); + } + } + + void stop_editing() { + if(_entry.is_visible()) { + remove_modal_grab(); + _entry.hide(); + _gl_widget.grab_focus(); + } + } + + GtkGLWidget &get_gl_widget() { + return _gl_widget; + } + +protected: + bool on_key_press_event(GdkEventKey *gdk_event) override { + if(is_editing()) { + if(gdk_event->keyval == GDK_KEY_Escape) { + return _gl_widget.event((GdkEvent *)gdk_event); + } else { + _entry.event((GdkEvent *)gdk_event); + } + return true; + } + + return Gtk::Fixed::on_key_press_event(gdk_event); + } + + bool on_key_release_event(GdkEventKey *gdk_event) override { + if(is_editing()) { + _entry.event((GdkEvent *)gdk_event); + return true; + } + + return Gtk::Fixed::on_key_release_event(gdk_event); + } + + void on_size_allocate(Gtk::Allocation& allocation) override { + Gtk::Fixed::on_size_allocate(allocation); + + _gl_widget.size_allocate(allocation); + + int width, height, min_height, natural_height; + _entry.get_size_request(width, height); + _entry.get_preferred_height(min_height, natural_height); + + Gtk::Allocation entry_rect = _entry.get_allocation(); + entry_rect.set_width(width); + entry_rect.set_height(natural_height); + _entry.size_allocate(entry_rect); + } + + void on_activate() { + if(_receiver->onEditingDone) { + _receiver->onEditingDone(_entry.get_text()); + } + } +}; + +class GtkWindow : public Gtk::Window { + Platform::Window *_receiver; + Gtk::VBox _vbox; + Gtk::MenuBar *_menu_bar = NULL; + Gtk::HBox _hbox; + GtkEditorOverlay _editor_overlay; + Gtk::VScrollbar _scrollbar; + bool _is_under_cursor = false; + bool _is_fullscreen = false; + std::string _tooltip_text; + Gdk::Rectangle _tooltip_area; + +public: + GtkWindow(Platform::Window *receiver) : _receiver(receiver), _editor_overlay(receiver) { + _hbox.pack_start(_editor_overlay, /*expand=*/true, /*fill=*/true); + _hbox.pack_end(_scrollbar, /*expand=*/false, /*fill=*/false); + _vbox.pack_end(_hbox, /*expand=*/true, /*fill=*/true); + add(_vbox); + + _vbox.show(); + _hbox.show(); + _editor_overlay.show(); + get_gl_widget().show(); + + _scrollbar.get_adjustment()->signal_value_changed(). + connect(sigc::mem_fun(this, &GtkWindow::on_scrollbar_value_changed)); + + get_gl_widget().set_has_tooltip(true); + get_gl_widget().signal_query_tooltip(). + connect(sigc::mem_fun(this, &GtkWindow::on_query_tooltip)); + } + + bool is_full_screen() const { + return _is_fullscreen; + } + + Gtk::MenuBar *get_menu_bar() const { + return _menu_bar; + } + + void set_menu_bar(Gtk::MenuBar *menu_bar) { + if(_menu_bar) { + _vbox.remove(*_menu_bar); + } + _menu_bar = menu_bar; + if(_menu_bar) { + _menu_bar->show_all(); + _vbox.pack_start(*_menu_bar, /*expand=*/false, /*fill=*/false); + } + } + + GtkEditorOverlay &get_editor_overlay() { + return _editor_overlay; + } + + GtkGLWidget &get_gl_widget() { + return _editor_overlay.get_gl_widget(); + } + + Gtk::VScrollbar &get_scrollbar() { + return _scrollbar; + } + + void set_tooltip(const std::string &text, const Gdk::Rectangle &rect) { + if(_tooltip_text != text) { + _tooltip_text = text; + _tooltip_area = rect; + get_gl_widget().trigger_tooltip_query(); + } + } + +protected: + bool on_query_tooltip(int x, int y, bool keyboard_tooltip, + const Glib::RefPtr &tooltip) { + tooltip->set_text(_tooltip_text); + tooltip->set_tip_area(_tooltip_area); + return !_tooltip_text.empty() && (keyboard_tooltip || _is_under_cursor); + } + + bool on_enter_notify_event(GdkEventCrossing* gdk_event) override { + _is_under_cursor = true; + + return true; + } + + bool on_leave_notify_event(GdkEventCrossing* gdk_event) override { + _is_under_cursor = false; + + return true; + } + + bool on_delete_event(GdkEventAny* gdk_event) override { + if(_receiver->onClose) { + _receiver->onClose(); + return true; + } + + return false; + } + + bool on_window_state_event(GdkEventWindowState *gdk_event) override { + _is_fullscreen = gdk_event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN; + if(_receiver->onFullScreen) { + _receiver->onFullScreen(_is_fullscreen); + } + + return true; + } + + void on_scrollbar_value_changed() { + if(_receiver->onScrollbarAdjusted) { + _receiver->onScrollbarAdjusted(_scrollbar.get_adjustment()->get_value()); + } + } +}; + +//----------------------------------------------------------------------------- +// Windows +//----------------------------------------------------------------------------- + +class WindowImplGtk final : public Window { +public: + GtkWindow gtkWindow; + MenuBarRef menuBar; + + WindowImplGtk(Window::Kind kind) : gtkWindow(this) { + switch(kind) { + case Kind::TOPLEVEL: + break; + + case Kind::TOOL: + gtkWindow.set_type_hint(Gdk::WINDOW_TYPE_HINT_UTILITY); + gtkWindow.set_skip_taskbar_hint(true); + gtkWindow.set_skip_pager_hint(true); + break; + } + + auto icon = LoadPng("freedesktop/solvespace-48x48.png"); + auto gdkIcon = + Gdk::Pixbuf::create_from_data(&icon->data[0], Gdk::COLORSPACE_RGB, + icon->format == Pixmap::Format::RGBA, 8, + icon->width, icon->height, icon->stride); + gtkWindow.set_icon(gdkIcon->copy()); + } + + double GetPixelDensity() override { + return gtkWindow.get_screen()->get_resolution(); + } + + int GetDevicePixelRatio() override { + return gtkWindow.get_scale_factor(); + } + + bool IsVisible() override { + return gtkWindow.is_visible(); + } + + void SetVisible(bool visible) override { + if(visible) { + gtkWindow.show(); + } else { + gtkWindow.hide(); + } + } + + void Focus() override { + gtkWindow.present(); + } + + bool IsFullScreen() override { + return gtkWindow.is_full_screen(); + } + + void SetFullScreen(bool fullScreen) override { + if(fullScreen) { + gtkWindow.fullscreen(); + } else { + gtkWindow.unfullscreen(); + } + } + + void SetTitle(const std::string &title) override { + gtkWindow.set_title(PrepareTitle(title)); + } + + void SetMenuBar(MenuBarRef newMenuBar) override { + if(newMenuBar) { + Gtk::MenuBar *gtkMenuBar = &((MenuBarImplGtk*)&*newMenuBar)->gtkMenuBar; + gtkWindow.set_menu_bar(gtkMenuBar); + } else { + gtkWindow.set_menu_bar(NULL); + } + menuBar = newMenuBar; + } + + void GetContentSize(double *width, double *height) override { + *width = gtkWindow.get_gl_widget().get_allocated_width(); + *height = gtkWindow.get_gl_widget().get_allocated_height(); + } + + void SetMinContentSize(double width, double height) override { + gtkWindow.get_gl_widget().set_size_request((int)width, (int)height); + } + + void FreezePosition(SettingsRef settings, const std::string &key) override { + if(!gtkWindow.is_visible()) return; + + int left, top, width, height; + gtkWindow.get_position(left, top); + gtkWindow.get_size(width, height); + bool isMaximized = gtkWindow.is_maximized(); + + settings->FreezeInt(key + "_Left", left); + settings->FreezeInt(key + "_Top", top); + settings->FreezeInt(key + "_Width", width); + settings->FreezeInt(key + "_Height", height); + settings->FreezeBool(key + "_Maximized", isMaximized); + } + + void ThawPosition(SettingsRef settings, const std::string &key) override { + int left, top, width, height; + gtkWindow.get_position(left, top); + gtkWindow.get_size(width, height); + + left = settings->ThawInt(key + "_Left", left); + top = settings->ThawInt(key + "_Top", top); + width = settings->ThawInt(key + "_Width", width); + height = settings->ThawInt(key + "_Height", height); + + gtkWindow.move(left, top); + gtkWindow.resize(width, height); + + if(settings->ThawBool(key + "_Maximized", false)) { + gtkWindow.maximize(); + } + } + + void SetCursor(Cursor cursor) override { + Gdk::CursorType gdkCursorType; + switch(cursor) { + case Cursor::POINTER: gdkCursorType = Gdk::ARROW; break; + case Cursor::HAND: gdkCursorType = Gdk::HAND1; break; + default: ssassert(false, "Unexpected cursor"); + } + + auto gdkWindow = gtkWindow.get_gl_widget().get_window(); + if(gdkWindow) { + gdkWindow->set_cursor(Gdk::Cursor::create(gdkCursorType)); + } + } + + void SetTooltip(const std::string &text, double x, double y, + double width, double height) override { + gtkWindow.set_tooltip(text, { (int)x, (int)y, (int)width, (int)height }); + } + + bool IsEditorVisible() override { + return gtkWindow.get_editor_overlay().is_editing(); + } + + void ShowEditor(double x, double y, double fontHeight, double minWidth, + bool isMonospace, const std::string &text) override { + gtkWindow.get_editor_overlay().start_editing( + (int)x, (int)y, (int)fontHeight, (int)minWidth, isMonospace, text); + } + + void HideEditor() override { + gtkWindow.get_editor_overlay().stop_editing(); + } + + void SetScrollbarVisible(bool visible) override { + if(visible) { + gtkWindow.get_scrollbar().show(); + } else { + gtkWindow.get_scrollbar().hide(); + } + } + + void ConfigureScrollbar(double min, double max, double pageSize) override { + auto adjustment = gtkWindow.get_scrollbar().get_adjustment(); + adjustment->configure(adjustment->get_value(), min, max, 1, 4, pageSize); + } + + double GetScrollbarPosition() override { + return gtkWindow.get_scrollbar().get_adjustment()->get_value(); + } + + void SetScrollbarPosition(double pos) override { + return gtkWindow.get_scrollbar().get_adjustment()->set_value(pos); + } + + void Invalidate() override { + gtkWindow.get_gl_widget().queue_render(); + } +}; + +WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { + auto window = std::make_shared(kind); + if(parentWindow) { + window->gtkWindow.set_transient_for( + std::static_pointer_cast(parentWindow)->gtkWindow); + } + return window; +} + +//----------------------------------------------------------------------------- +// 3DConnexion support +//----------------------------------------------------------------------------- + +void Open3DConnexion() {} +void Close3DConnexion() {} + +#if defined(HAVE_SPACEWARE) && defined(GDK_WINDOWING_X11) +static GdkFilterReturn GdkSpnavFilter(GdkXEvent *gdkXEvent, GdkEvent *gdkEvent, gpointer data) { + XEvent *xEvent = (XEvent *)gdkXEvent; + WindowImplGtk *window = (WindowImplGtk *)data; + + spnav_event spnavEvent; + if(!spnav_x11_event(xEvent, &spnavEvent)) { + return GDK_FILTER_CONTINUE; + } + + switch(spnavEvent.type) { + case SPNAV_EVENT_MOTION: { + SixDofEvent event = {}; + event.type = SixDofEvent::Type::MOTION; + event.translationX = (double)spnavEvent.motion.x; + event.translationY = (double)spnavEvent.motion.y; + event.translationZ = (double)spnavEvent.motion.z * -1.0; + event.rotationX = (double)spnavEvent.motion.rx * 0.001; + event.rotationY = (double)spnavEvent.motion.ry * 0.001; + event.rotationZ = (double)spnavEvent.motion.rz * -0.001; + event.shiftDown = xEvent->xmotion.state & ShiftMask; + event.controlDown = xEvent->xmotion.state & ControlMask; + if(window->onSixDofEvent) { + window->onSixDofEvent(event); + } + break; + } + + case SPNAV_EVENT_BUTTON: + SixDofEvent event = {}; + if(spnavEvent.button.press) { + event.type = SixDofEvent::Type::PRESS; + } else { + event.type = SixDofEvent::Type::RELEASE; + } + switch(spnavEvent.button.bnum) { + case 0: event.button = SixDofEvent::Button::FIT; break; + default: return GDK_FILTER_REMOVE; + } + event.shiftDown = xEvent->xmotion.state & ShiftMask; + event.controlDown = xEvent->xmotion.state & ControlMask; + if(window->onSixDofEvent) { + window->onSixDofEvent(event); + } + break; + } + + return GDK_FILTER_REMOVE; +} + +void Request3DConnexionEventsForWindow(WindowRef window) { + std::shared_ptr windowImpl = + std::static_pointer_cast(window); + + Glib::RefPtr gdkWindow = windowImpl->gtkWindow.get_window(); + if(GDK_IS_X11_DISPLAY(gdkWindow->get_display()->gobj())) { + gdkWindow->add_filter(GdkSpnavFilter, windowImpl.get()); + spnav_x11_open(gdk_x11_get_default_xdisplay(), + gdk_x11_window_get_xid(gdkWindow->gobj())); + } +} +#else +void Request3DConnexionEventsForWindow(WindowRef window) {} +#endif + +//----------------------------------------------------------------------------- +// Message dialogs +//----------------------------------------------------------------------------- + +class MessageDialogImplGtk; + +static std::vector> shownMessageDialogs; + +class MessageDialogImplGtk final : public MessageDialog, + public std::enable_shared_from_this { +public: + Gtk::Image gtkImage; + Gtk::MessageDialog gtkDialog; + + MessageDialogImplGtk(Gtk::Window &parent) + : gtkDialog(parent, "", /*use_markup=*/false, Gtk::MESSAGE_INFO, + Gtk::BUTTONS_NONE, /*modal=*/true) + { + SetTitle("Message"); + } + + void SetType(Type type) override { + switch(type) { + case Type::INFORMATION: + gtkImage.set_from_icon_name("dialog-information", Gtk::ICON_SIZE_DIALOG); + break; + + case Type::QUESTION: + gtkImage.set_from_icon_name("dialog-question", Gtk::ICON_SIZE_DIALOG); + break; + + case Type::WARNING: + gtkImage.set_from_icon_name("dialog-warning", Gtk::ICON_SIZE_DIALOG); + break; + + case Type::ERROR: + gtkImage.set_from_icon_name("dialog-error", Gtk::ICON_SIZE_DIALOG); + break; + } + gtkDialog.set_image(gtkImage); + } + + void SetTitle(std::string title) override { + gtkDialog.set_title(PrepareTitle(title)); + } + + void SetMessage(std::string message) override { + gtkDialog.set_message(message); + } + + void SetDescription(std::string description) override { + gtkDialog.set_secondary_text(description); + } + + void AddButton(std::string label, Response response, bool isDefault) override { + int responseId = 0; + switch(response) { + case Response::NONE: ssassert(false, "Unexpected response"); + case Response::OK: responseId = Gtk::RESPONSE_OK; break; + case Response::YES: responseId = Gtk::RESPONSE_YES; break; + case Response::NO: responseId = Gtk::RESPONSE_NO; break; + case Response::CANCEL: responseId = Gtk::RESPONSE_CANCEL; break; + } + gtkDialog.add_button(PrepareMnemonics(label), responseId); + if(isDefault) { + gtkDialog.set_default_response(responseId); + } + } + + Response ProcessResponse(int gtkResponse) { + Response response; + switch(gtkResponse) { + case Gtk::RESPONSE_OK: response = Response::OK; break; + case Gtk::RESPONSE_YES: response = Response::YES; break; + case Gtk::RESPONSE_NO: response = Response::NO; break; + case Gtk::RESPONSE_CANCEL: response = Response::CANCEL; break; + + case Gtk::RESPONSE_NONE: + case Gtk::RESPONSE_CLOSE: + case Gtk::RESPONSE_DELETE_EVENT: + response = Response::NONE; + break; + + default: ssassert(false, "Unexpected response"); + } + + if(onResponse) { + onResponse(response); + } + return response; + } + + void ShowModal() override { + gtkDialog.signal_hide().connect([this] { + auto it = std::remove(shownMessageDialogs.begin(), shownMessageDialogs.end(), + shared_from_this()); + shownMessageDialogs.erase(it); + }); + shownMessageDialogs.push_back(shared_from_this()); + + gtkDialog.signal_response().connect([this](int gtkResponse) { + ProcessResponse(gtkResponse); + gtkDialog.hide(); + }); + gtkDialog.show(); + } + + Response RunModal() override { + return ProcessResponse(gtkDialog.run()); + } +}; + +MessageDialogRef CreateMessageDialog(WindowRef parentWindow) { + return std::make_shared( + std::static_pointer_cast(parentWindow)->gtkWindow); +} + +//----------------------------------------------------------------------------- +// File dialogs +//----------------------------------------------------------------------------- + +class FileDialogImplGtk : public FileDialog { +public: + Gtk::FileChooser *gtkChooser; + std::vector extensions; + + void InitFileChooser(Gtk::FileChooser &chooser) { + gtkChooser = &chooser; + gtkChooser->property_filter().signal_changed(). + connect(sigc::mem_fun(this, &FileDialogImplGtk::FilterChanged)); + } + + void SetCurrentName(std::string name) override { + gtkChooser->set_current_name(name); + } + + Platform::Path GetFilename() override { + return Path::From(gtkChooser->get_filename()); + } + + void SetFilename(Platform::Path path) override { + gtkChooser->set_filename(path.raw); + } + + void AddFilter(std::string name, std::vector extensions) override { + Glib::RefPtr gtkFilter = Gtk::FileFilter::create(); + Glib::ustring desc; + for(auto extension : extensions) { + Glib::ustring pattern = "*"; + if(!extension.empty()) { + pattern = "*." + extension; + gtkFilter->add_pattern(pattern); + gtkFilter->add_pattern(Glib::ustring(pattern).uppercase()); + } + if(!desc.empty()) { + desc += ", "; + } + desc += pattern; + } + gtkFilter->set_name(name + " (" + desc + ")"); + + this->extensions.push_back(extensions.front()); + gtkChooser->add_filter(gtkFilter); + } + + std::string GetExtension() { + auto filters = gtkChooser->list_filters(); + size_t filterIndex = + std::find(filters.begin(), filters.end(), gtkChooser->get_filter()) - + filters.begin(); + if(filterIndex < extensions.size()) { + return extensions[filterIndex]; + } else { + return extensions.front(); + } + } + + void SetExtension(std::string extension) { + auto filters = gtkChooser->list_filters(); + size_t extensionIndex = + std::find(extensions.begin(), extensions.end(), extension) - + extensions.begin(); + if(extensionIndex < filters.size()) { + gtkChooser->set_filter(filters[extensionIndex]); + } else { + gtkChooser->set_filter(filters.front()); + } + } + + void FilterChanged() { + std::string extension = GetExtension(); + if(extension.empty()) + return; + + Platform::Path path = GetFilename(); + SetCurrentName(path.WithExtension(extension).FileName()); + } + + void FreezeChoices(SettingsRef settings, const std::string &key) override { + settings->FreezeString("Dialog_" + key + "_Folder", + gtkChooser->get_current_folder()); + settings->FreezeString("Dialog_" + key + "_Filter", GetExtension()); + } + + void ThawChoices(SettingsRef settings, const std::string &key) override { + gtkChooser->set_current_folder(settings->ThawString("Dialog_" + key + "_Folder")); + SetExtension(settings->ThawString("Dialog_" + key + "_Filter")); + } + + void CheckForUntitledFile() { + if(gtkChooser->get_action() == Gtk::FILE_CHOOSER_ACTION_SAVE && + Path::From(gtkChooser->get_current_name()).FileStem().empty()) { + gtkChooser->set_current_name(std::string(_("untitled")) + "." + GetExtension()); + } + } +}; + +class FileDialogGtkImplGtk final : public FileDialogImplGtk { +public: + Gtk::FileChooserDialog gtkDialog; + + FileDialogGtkImplGtk(Gtk::Window >kParent, bool isSave) + : gtkDialog(gtkParent, + isSave ? C_("title", "Save File") + : C_("title", "Open File"), + isSave ? Gtk::FILE_CHOOSER_ACTION_SAVE + : Gtk::FILE_CHOOSER_ACTION_OPEN) { + gtkDialog.add_button(C_("button", "_Cancel"), Gtk::RESPONSE_CANCEL); + gtkDialog.add_button(isSave ? C_("button", "_Save") + : C_("button", "_Open"), Gtk::RESPONSE_OK); + gtkDialog.set_default_response(Gtk::RESPONSE_OK); + InitFileChooser(gtkDialog); + } + + void SetTitle(std::string title) override { + gtkDialog.set_title(PrepareTitle(title)); + } + + bool RunModal() override { + CheckForUntitledFile(); + if(gtkDialog.run() == Gtk::RESPONSE_OK) { + return true; + } else { + return false; + } + } +}; + +#if defined(HAVE_GTK_FILECHOOSERNATIVE) + +class FileDialogNativeImplGtk final : public FileDialogImplGtk { +public: + Glib::RefPtr gtkNative; + + FileDialogNativeImplGtk(Gtk::Window >kParent, bool isSave) { + gtkNative = Gtk::FileChooserNative::create( + isSave ? C_("title", "Save File") + : C_("title", "Open File"), + gtkParent, + isSave ? Gtk::FILE_CHOOSER_ACTION_SAVE + : Gtk::FILE_CHOOSER_ACTION_OPEN, + isSave ? C_("button", "_Save") + : C_("button", "_Open"), + C_("button", "_Cancel")); + // Seriously, GTK?! + InitFileChooser(*gtkNative.operator->()); + } + + void SetTitle(std::string title) override { + gtkNative->set_title(PrepareTitle(title)); + } + + bool RunModal() override { + CheckForUntitledFile(); + if(gtkNative->run() == Gtk::RESPONSE_ACCEPT) { + return true; + } else { + return false; + } + } +}; + +#endif + +#if defined(HAVE_GTK_FILECHOOSERNATIVE) +# define FILE_DIALOG_IMPL FileDialogNativeImplGtk +#else +# define FILE_DIALOG_IMPL FileDialogGtkImplGtk +#endif + +FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) { + Gtk::Window >kParent = std::static_pointer_cast(parentWindow)->gtkWindow; + return std::make_shared(gtkParent, /*isSave=*/false); +} + +FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) { + Gtk::Window >kParent = std::static_pointer_cast(parentWindow)->gtkWindow; + return std::make_shared(gtkParent, /*isSave=*/true); +} + +//----------------------------------------------------------------------------- +// Application-wide APIs +//----------------------------------------------------------------------------- + +std::vector GetFontFiles() { + std::vector fonts; + + // fontconfig is already initialized by GTK + FcPattern *pat = FcPatternCreate(); + FcObjectSet *os = FcObjectSetBuild(FC_FILE, (char *)0); + FcFontSet *fs = FcFontList(0, pat, os); + + for(int i = 0; i < fs->nfont; i++) { + FcChar8 *filenameFC = FcPatternFormat(fs->fonts[i], (const FcChar8*) "%{file}"); + fonts.push_back(Platform::Path::From((const char *)filenameFC)); + FcStrFree(filenameFC); + } + + FcFontSetDestroy(fs); + FcObjectSetDestroy(os); + FcPatternDestroy(pat); + + return fonts; +} + +void OpenInBrowser(const std::string &url) { + gtk_show_uri(Gdk::Screen::get_default()->gobj(), url.c_str(), GDK_CURRENT_TIME, NULL); +} + +Gtk::Main *gtkMain; + +std::vector InitGui(int argc, char **argv) { + // It would in principle be possible to judiciously use Glib::filename_{from,to}_utf8, + // but it's not really worth the effort. + // The setlocale() call is necessary for Glib::get_charset() to detect the system + // character set; otherwise it thinks it is always ANSI_X3.4-1968. + // We set it back to C after all so that printf() and friends behave in a consistent way. + setlocale(LC_ALL, ""); + if(!Glib::get_charset()) { + dbp("Sorry, only UTF-8 locales are supported."); + exit(1); + } + setlocale(LC_ALL, "C"); + + // Let GTK parse arguments and update argc/argv. (They're passed by reference.) + gtkMain = new Gtk::Main(argc, argv, /*set_locale=*/false); + + // Now that GTK arguments are removed, grab arguments for ourselves. + std::vector args = InitCli(argc, argv); + + // Add our application-specific styles, to override GTK defaults. + Glib::RefPtr style_provider = Gtk::CssProvider::create(); + style_provider->load_from_data(R"( + entry { + background: white; + color: black; + } + )"); + Gtk::StyleContext::add_provider_for_screen( + Gdk::Screen::get_default(), style_provider, + 600 /*Gtk::STYLE_PROVIDER_PRIORITY_APPLICATION*/); + + // Set locale from user preferences. + // This apparently only consults the LANGUAGE environment variable. + const char* const* langNames = g_get_language_names(); + while(*langNames) { + if(SetLocale(*langNames++)) break; + } + if(!*langNames) { + SetLocale("en_US"); + } + + return args; +} + +void RunGui() { + Gtk::Main::run(); +} + +void ExitGui() { + Gtk::Main::quit(); +} + +void ClearGui() { + delete gtkMain; +} + +} +} diff --git a/src/platform/guimac.mm b/src/platform/guimac.mm new file mode 100644 index 0000000..5aa7f05 --- /dev/null +++ b/src/platform/guimac.mm @@ -0,0 +1,1494 @@ +//----------------------------------------------------------------------------- +// The Cocoa-based implementation of platform-dependent GUI functionality. +// +// Copyright 2018 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" +#import + +using namespace SolveSpace; + +//----------------------------------------------------------------------------- +// Internal AppKit classes +//----------------------------------------------------------------------------- + +@interface NSToolTipManager : NSObject ++ (NSToolTipManager *)sharedToolTipManager; +- (void)setInitialToolTipDelay:(double)delay; +- (void)orderOutToolTip; +- (void)abortToolTip; +- (void)_displayTemporaryToolTipForView:(id)arg1 withString:(id)arg2; +@end + +//----------------------------------------------------------------------------- +// Objective-C bridging +//----------------------------------------------------------------------------- + +static NSString* Wrap(const std::string &s) { + return [NSString stringWithUTF8String:s.c_str()]; +} + +@interface SSFunction : NSObject +- (SSFunction *)initWithFunction:(std::function *)aFunc; +- (void)run; +@end + +@implementation SSFunction +{ + std::function *func; +} + +- (SSFunction *)initWithFunction:(std::function *)aFunc { + if(self = [super init]) { + func = aFunc; + } + return self; +} + +- (void)run { + if(*func) (*func)(); +} +@end + +namespace SolveSpace { +namespace Platform { + +//----------------------------------------------------------------------------- +// Utility functions +//----------------------------------------------------------------------------- + +static std::string PrepareMnemonics(std::string label) { + // OS X does not support mnemonics + label.erase(std::remove(label.begin(), label.end(), '&'), label.end()); + return label; +} + +//----------------------------------------------------------------------------- +// Fatal errors +//----------------------------------------------------------------------------- + +// This gets put into the "Application Specific Information" field in crash +// reporter dialog. +typedef struct { + unsigned version __attribute__((aligned(8))); + const char *message __attribute__((aligned(8))); + const char *signature __attribute__((aligned(8))); + const char *backtrace __attribute__((aligned(8))); + const char *message2 __attribute__((aligned(8))); + void *reserved __attribute__((aligned(8))); + void *reserved2 __attribute__((aligned(8))); +} crash_info_t; + +#define CRASH_VERSION 4 + +crash_info_t crashAnnotation __attribute__((section("__DATA,__crash_info"))) = { + CRASH_VERSION, NULL, NULL, NULL, NULL, NULL, NULL +}; + +void FatalError(const std::string &message) { + crashAnnotation.message = message.c_str(); + abort(); +} + +//----------------------------------------------------------------------------- +// Settings +//----------------------------------------------------------------------------- + +class SettingsImplCocoa final : public Settings { +public: + NSUserDefaults *userDefaults; + + SettingsImplCocoa() { + userDefaults = [NSUserDefaults standardUserDefaults]; + } + + void FreezeInt(const std::string &key, uint32_t value) override { + [userDefaults setInteger:value forKey:Wrap(key)]; + } + + uint32_t ThawInt(const std::string &key, uint32_t defaultValue = 0) override { + NSString *nsKey = Wrap(key); + if([userDefaults objectForKey:nsKey]) { + return [userDefaults integerForKey:nsKey]; + } + return defaultValue; + } + + void FreezeBool(const std::string &key, bool value) override { + [userDefaults setBool:value forKey:Wrap(key)]; + } + + bool ThawBool(const std::string &key, bool defaultValue = false) override { + NSString *nsKey = Wrap(key); + if([userDefaults objectForKey:nsKey]) { + return [userDefaults boolForKey:nsKey]; + } + return defaultValue; + } + + void FreezeFloat(const std::string &key, double value) override { + [userDefaults setDouble:value forKey:Wrap(key)]; + } + + double ThawFloat(const std::string &key, double defaultValue = 0.0) override { + NSString *nsKey = Wrap(key); + if([userDefaults objectForKey:nsKey]) { + return [userDefaults doubleForKey:nsKey]; + } + return defaultValue; + } + + void FreezeString(const std::string &key, const std::string &value) override { + [userDefaults setObject:Wrap(value) forKey:Wrap(key)]; + } + + std::string ThawString(const std::string &key, + const std::string &defaultValue = "") override { + NSObject *nsValue = [userDefaults objectForKey:Wrap(key)]; + if(nsValue && [nsValue isKindOfClass:[NSString class]]) { + return [(NSString *)nsValue UTF8String]; + } + return defaultValue; + } +}; + +SettingsRef GetSettings() { + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// Timers +//----------------------------------------------------------------------------- + +class TimerImplCocoa final : public Timer { +public: + NSTimer *timer; + + TimerImplCocoa() : timer(NULL) {} + + void RunAfter(unsigned milliseconds) override { + SSFunction *callback = [[SSFunction alloc] initWithFunction:&this->onTimeout]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: + [callback methodSignatureForSelector:@selector(run)]]; + invocation.target = callback; + invocation.selector = @selector(run); + + if(timer != NULL) { + [timer invalidate]; + } + timer = [NSTimer scheduledTimerWithTimeInterval:(milliseconds / 1000.0) + invocation:invocation repeats:NO]; + } + + ~TimerImplCocoa() { + if(timer != NULL) { + [timer invalidate]; + } + } +}; + +TimerRef CreateTimer() { + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// Menus +//----------------------------------------------------------------------------- + +class MenuItemImplCocoa final : public MenuItem { +public: + SSFunction *ssFunction; + NSMenuItem *nsMenuItem; + + MenuItemImplCocoa() { + ssFunction = [[SSFunction alloc] initWithFunction:&onTrigger]; + nsMenuItem = [[NSMenuItem alloc] initWithTitle:@"" + action:@selector(run) keyEquivalent:@""]; + nsMenuItem.target = ssFunction; + } + + void SetAccelerator(KeyboardEvent accel) override { + unichar accelChar; + switch(accel.key) { + case KeyboardEvent::Key::CHARACTER: + if(accel.chr == NSDeleteCharacter) { + accelChar = NSBackspaceCharacter; + } else { + accelChar = accel.chr; + } + break; + + case KeyboardEvent::Key::FUNCTION: + accelChar = NSF1FunctionKey + accel.num - 1; + break; + } + nsMenuItem.keyEquivalent = [[NSString alloc] initWithCharacters:&accelChar length:1]; + + NSUInteger modifierMask = 0; + if(accel.shiftDown) + modifierMask |= NSEventModifierFlagShift; + if(accel.controlDown) + modifierMask |= NSEventModifierFlagCommand; + nsMenuItem.keyEquivalentModifierMask = modifierMask; + } + + void SetIndicator(Indicator state) override { + // macOS does not support radio menu items + } + + void SetActive(bool active) override { + nsMenuItem.state = active ? NSControlStateValueOn : NSControlStateValueOff; + } + + void SetEnabled(bool enabled) override { + nsMenuItem.enabled = enabled; + } +}; + +class MenuImplCocoa final : public Menu { +public: + NSMenu *nsMenu; + + std::vector> menuItems; + std::vector> subMenus; + + MenuImplCocoa() { + nsMenu = [[NSMenu alloc] initWithTitle:@""]; + [nsMenu setAutoenablesItems:NO]; + } + + MenuItemRef AddItem(const std::string &label, + std::function onTrigger = NULL, + bool mnemonics = true) override { + auto menuItem = std::make_shared(); + menuItems.push_back(menuItem); + + menuItem->onTrigger = onTrigger; + [menuItem->nsMenuItem setTitle:Wrap(mnemonics ? PrepareMnemonics(label) : label)]; + [nsMenu addItem:menuItem->nsMenuItem]; + + return menuItem; + } + + MenuRef AddSubMenu(const std::string &label) override { + auto subMenu = std::make_shared(); + subMenus.push_back(subMenu); + + NSMenuItem *nsMenuItem = + [nsMenu addItemWithTitle:Wrap(PrepareMnemonics(label)) action:nil keyEquivalent:@""]; + [nsMenu setSubmenu:subMenu->nsMenu forItem:nsMenuItem]; + + return subMenu; + } + + void AddSeparator() override { + [nsMenu addItem:[NSMenuItem separatorItem]]; + } + + void PopUp() override { + [NSMenu popUpContextMenu:nsMenu withEvent:[NSApp currentEvent] forView:nil]; + } + + void Clear() override { + [nsMenu removeAllItems]; + menuItems.clear(); + subMenus.clear(); + } +}; + +MenuRef CreateMenu() { + return std::make_shared(); +} + +class MenuBarImplCocoa final : public MenuBar { +public: + NSMenu *nsMenuBar; + + std::vector> subMenus; + + MenuBarImplCocoa() { + nsMenuBar = [NSApp mainMenu]; + } + + MenuRef AddSubMenu(const std::string &label) override { + auto subMenu = std::make_shared(); + subMenus.push_back(subMenu); + + NSMenuItem *nsMenuItem = [nsMenuBar addItemWithTitle:@"" action:nil keyEquivalent:@""]; + [subMenu->nsMenu setTitle:Wrap(PrepareMnemonics(label))]; + [nsMenuBar setSubmenu:subMenu->nsMenu forItem:nsMenuItem]; + + return subMenu; + } + + void Clear() override { + while([nsMenuBar numberOfItems] != 1) { + [nsMenuBar removeItemAtIndex:1]; + } + subMenus.clear(); + } +}; + +MenuBarRef GetOrCreateMainMenu(bool *unique) { + static std::shared_ptr mainMenu; + if(!mainMenu) { + mainMenu = std::make_shared(); + } + *unique = true; + return mainMenu; +} + +} +} + +//----------------------------------------------------------------------------- +// Cocoa NSView and NSWindow extensions +//----------------------------------------------------------------------------- + +@interface SSView : NSOpenGLView +@property Platform::Window *receiver; + +@property BOOL acceptsFirstResponder; + +@property(readonly, getter=isEditing) BOOL editing; +- (void)startEditing:(NSString *)text at:(NSPoint)origin + withHeight:(double)fontHeight minWidth:(double)minWidth + usingMonospace:(BOOL)isMonospace; +- (void)stopEditing; +- (void)didEdit:(NSString *)text; + +@property double scrollerMin; +@property double scrollerMax; +@end + +@implementation SSView +{ + NSTrackingArea *trackingArea; + NSTextField *editor; +} + +@synthesize acceptsFirstResponder; + +- (id)initWithFrame:(NSRect)frameRect { + NSOpenGLPixelFormatAttribute attrs[] = { + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAColorSize, 24, + NSOpenGLPFADepthSize, 24, + 0 + }; + NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + if(self = [super initWithFrame:frameRect pixelFormat:pixelFormat]) { + self.wantsBestResolutionOpenGLSurface = YES; + self.wantsLayer = YES; + editor = [[NSTextField alloc] init]; + editor.editable = YES; + [[editor cell] setWraps:NO]; + [[editor cell] setScrollable:YES]; + editor.bezeled = NO; + editor.target = self; + editor.action = @selector(didEdit:); + } + return self; +} + +- (void)dealloc { +} + +- (BOOL)isFlipped { + return YES; +} + +@synthesize receiver; + +- (void)drawRect:(NSRect)aRect { + [[self openGLContext] makeCurrentContext]; + if(receiver->onRender) { + receiver->onRender(); + } + [[self openGLContext] flushBuffer]; +} + +- (BOOL)acceptsFirstMouse:(NSEvent *)event { + return YES; +} + +- (void)updateTrackingAreas { + [self removeTrackingArea:trackingArea]; + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] + options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | + ([self acceptsFirstResponder] + ? NSTrackingActiveInKeyWindow + : NSTrackingActiveAlways)) + owner:self userInfo:nil]; + [self addTrackingArea:trackingArea]; + [super updateTrackingAreas]; +} + +- (Platform::MouseEvent)convertMouseEvent:(NSEvent *)nsEvent { + Platform::MouseEvent event = {}; + + NSPoint nsPoint = [self convertPoint:nsEvent.locationInWindow fromView:self]; + event.x = nsPoint.x; + event.y = self.bounds.size.height - nsPoint.y; + + NSUInteger nsFlags = [nsEvent modifierFlags]; + if(nsFlags & NSEventModifierFlagShift) event.shiftDown = true; + if(nsFlags & NSEventModifierFlagCommand) event.controlDown = true; + + return event; +} + +- (void)mouseMotionEvent:(NSEvent *)nsEvent { + using Platform::MouseEvent; + + MouseEvent event = [self convertMouseEvent:nsEvent]; + event.type = MouseEvent::Type::MOTION; + event.button = MouseEvent::Button::NONE; + + NSUInteger nsButtons = [NSEvent pressedMouseButtons]; + if(nsButtons & (1 << 0)) { + event.button = MouseEvent::Button::LEFT; + } else if(nsButtons & (1 << 1)) { + event.button = MouseEvent::Button::RIGHT; + } else if(nsButtons & (1 << 2)) { + event.button = MouseEvent::Button::MIDDLE; + } + + if(receiver->onMouseEvent) { + receiver->onMouseEvent(event); + } +} + +- (void)mouseMotionEvent:(NSEvent *)nsEvent withButton:(Platform::MouseEvent::Button)button { + using Platform::MouseEvent; + + MouseEvent event = [self convertMouseEvent:nsEvent]; + event.type = MouseEvent::Type::MOTION; + event.button = button; + + if(receiver->onMouseEvent) { + receiver->onMouseEvent(event); + } +} + +- (void)mouseMoved:(NSEvent *)nsEvent { + [self mouseMotionEvent:nsEvent]; + [super mouseMoved:nsEvent]; +} + +- (void)mouseDragged:(NSEvent *)nsEvent { + [self mouseMotionEvent:nsEvent withButton:Platform::MouseEvent::Button::LEFT]; +} + +- (void)otherMouseDragged:(NSEvent *)nsEvent { + [self mouseMotionEvent:nsEvent]; +} + +- (void)rightMouseDragged:(NSEvent *)nsEvent { + [self mouseMotionEvent:nsEvent]; +} + +- (void)mouseButtonEvent:(NSEvent *)nsEvent withType:(Platform::MouseEvent::Type)type { + using Platform::MouseEvent; + + MouseEvent event = [self convertMouseEvent:nsEvent]; + event.type = type; + if([nsEvent buttonNumber] == 0) { + event.button = MouseEvent::Button::LEFT; + } else if([nsEvent buttonNumber] == 1) { + event.button = MouseEvent::Button::RIGHT; + } else if([nsEvent buttonNumber] == 2) { + event.button = MouseEvent::Button::MIDDLE; + } else return; + + if(receiver->onMouseEvent) { + receiver->onMouseEvent(event); + } +} + +- (void)mouseDownEvent:(NSEvent *)nsEvent { + using Platform::MouseEvent; + + MouseEvent::Type type; + if([nsEvent clickCount] == 1) { + type = MouseEvent::Type::PRESS; + } else { + type = MouseEvent::Type::DBL_PRESS; + } + [self mouseButtonEvent:nsEvent withType:type]; +} + +- (void)mouseUpEvent:(NSEvent *)nsEvent { + using Platform::MouseEvent; + + [self mouseButtonEvent:nsEvent withType:(MouseEvent::Type::RELEASE)]; +} + +- (void)mouseDown:(NSEvent *)nsEvent { + [self mouseDownEvent:nsEvent]; +} + +- (void)otherMouseDown:(NSEvent *)nsEvent { + [self mouseDownEvent:nsEvent]; +} + +- (void)rightMouseDown:(NSEvent *)nsEvent { + [self mouseDownEvent:nsEvent]; + [super rightMouseDown:nsEvent]; +} + +- (void)mouseUp:(NSEvent *)nsEvent { + [self mouseUpEvent:nsEvent]; +} + +- (void)otherMouseUp:(NSEvent *)nsEvent { + [self mouseUpEvent:nsEvent]; +} + +- (void)rightMouseUp:(NSEvent *)nsEvent { + [self mouseUpEvent:nsEvent]; +} + +- (void)scrollWheel:(NSEvent *)nsEvent { + using Platform::MouseEvent; + + MouseEvent event = [self convertMouseEvent:nsEvent]; + event.type = MouseEvent::Type::SCROLL_VERT; + event.scrollDelta = [nsEvent deltaY]; + + if(receiver->onMouseEvent) { + receiver->onMouseEvent(event); + } +} + +- (void)mouseExited:(NSEvent *)nsEvent { + using Platform::MouseEvent; + + MouseEvent event = [self convertMouseEvent:nsEvent]; + event.type = MouseEvent::Type::LEAVE; + + if(receiver->onMouseEvent) { + receiver->onMouseEvent(event); + } +} + +- (Platform::KeyboardEvent)convertKeyboardEvent:(NSEvent *)nsEvent { + using Platform::KeyboardEvent; + + KeyboardEvent event = {}; + + NSUInteger nsFlags = [nsEvent modifierFlags]; + if(nsFlags & NSEventModifierFlagShift) + event.shiftDown = true; + if(nsFlags & NSEventModifierFlagCommand) + event.controlDown = true; + + unichar chr = 0; + if(NSString *nsChr = [[nsEvent charactersIgnoringModifiers] lowercaseString]) { + chr = [nsChr characterAtIndex:0]; + } + if(chr >= NSF1FunctionKey && chr <= NSF12FunctionKey) { + event.key = KeyboardEvent::Key::FUNCTION; + event.num = chr - NSF1FunctionKey + 1; + } else { + event.key = KeyboardEvent::Key::CHARACTER; + event.chr = chr; + } + + return event; +} + +- (void)keyDown:(NSEvent *)nsEvent { + using Platform::KeyboardEvent; + + if([NSEvent modifierFlags] & ~(NSEventModifierFlagShift|NSEventModifierFlagCommand)) { + [super keyDown:nsEvent]; + return; + } + + KeyboardEvent event = [self convertKeyboardEvent:nsEvent]; + event.type = KeyboardEvent::Type::PRESS; + + if(receiver->onKeyboardEvent) { + receiver->onKeyboardEvent(event); + return; + } + + [super keyDown:nsEvent]; +} + +- (void)keyUp:(NSEvent *)nsEvent { + using Platform::KeyboardEvent; + + if([NSEvent modifierFlags] & ~(NSEventModifierFlagShift|NSEventModifierFlagCommand)) { + [super keyUp:nsEvent]; + return; + } + + KeyboardEvent event = [self convertKeyboardEvent:nsEvent]; + event.type = KeyboardEvent::Type::RELEASE; + + if(receiver->onKeyboardEvent) { + receiver->onKeyboardEvent(event); + return; + } + + [super keyUp:nsEvent]; +} + +@synthesize editing; + +- (void)startEditing:(NSString *)text at:(NSPoint)origin withHeight:(double)fontHeight + minWidth:(double)minWidth usingMonospace:(BOOL)isMonospace { + if(!editing) { + [self addSubview:editor]; + editing = YES; + } + + if(isMonospace) { + editor.font = [NSFont fontWithName:@"Monaco" size:fontHeight]; + } else { + editor.font = [NSFont controlContentFontOfSize:fontHeight]; + } + + origin.x -= 3; /* left padding; no way to get it from NSTextField */ + origin.y -= [editor intrinsicContentSize].height; + origin.y += [editor baselineOffsetFromBottom]; + + [editor setFrameOrigin:origin]; + [editor setStringValue:text]; + [editor sizeToFit]; + + NSSize frameSize = [editor frame].size; + frameSize.width = std::max(frameSize.width, minWidth); + [editor setFrameSize:frameSize]; + + [[self window] makeFirstResponder:editor]; + [[self window] makeKeyWindow]; +} + +- (void)stopEditing { + if(editing) { + [editor removeFromSuperview]; + [[self window] makeFirstResponder:self]; + editing = NO; + } +} + +- (void)didEdit:(id)sender { + if(receiver->onEditingDone) { + receiver->onEditingDone([[editor stringValue] UTF8String]); + } +} + +- (void)cancelOperation:(id)sender { + using Platform::KeyboardEvent; + + if(receiver->onKeyboardEvent) { + KeyboardEvent event = {}; + event.key = KeyboardEvent::Key::CHARACTER; + event.chr = '\e'; + event.type = KeyboardEvent::Type::PRESS; + receiver->onKeyboardEvent(event); + event.type = KeyboardEvent::Type::RELEASE; + receiver->onKeyboardEvent(event); + } +} + +@synthesize scrollerMin; +@synthesize scrollerMax; + +- (void)didScroll:(NSScroller *)sender { + if(receiver->onScrollbarAdjusted) { + double pos = scrollerMin + [sender doubleValue] * (scrollerMax - scrollerMin); + receiver->onScrollbarAdjusted(pos); + } +} +@end + +@interface SSWindowDelegate : NSObject +@property Platform::Window *receiver; + +- (BOOL)windowShouldClose:(id)sender; + +@property(readonly, getter=isFullScreen) BOOL fullScreen; +- (void)windowDidEnterFullScreen:(NSNotification *)notification; +- (void)windowDidExitFullScreen:(NSNotification *)notification; +@end + +@implementation SSWindowDelegate +@synthesize receiver; + +- (BOOL)windowShouldClose:(id)sender { + if(receiver->onClose) { + receiver->onClose(); + } + return NO; +} + +@synthesize fullScreen; + +- (void)windowDidEnterFullScreen:(NSNotification *)notification { + fullScreen = true; + if(receiver->onFullScreen) { + receiver->onFullScreen(fullScreen); + } +} + +- (void)windowDidExitFullScreen:(NSNotification *)notification { + fullScreen = false; + if(receiver->onFullScreen) { + receiver->onFullScreen(fullScreen); + } +} +@end + +namespace SolveSpace { +namespace Platform { + +//----------------------------------------------------------------------------- +// Windows +//----------------------------------------------------------------------------- + +class WindowImplCocoa final : public Window { +public: + NSWindow *nsWindow; + SSWindowDelegate *ssWindowDelegate; + SSView *ssView; + NSScroller *nsScroller; + NSView *nsContainer; + + NSArray *nsConstraintsWithScrollbar; + NSArray *nsConstraintsWithoutScrollbar; + + double minWidth = 100.0; + double minHeight = 100.0; + + NSString *nsToolTip; + + WindowImplCocoa(Window::Kind kind, std::shared_ptr parentWindow) { + ssView = [[SSView alloc] init]; + ssView.translatesAutoresizingMaskIntoConstraints = NO; + ssView.receiver = this; + + nsScroller = [[NSScroller alloc] initWithFrame:NSMakeRect(0, 0, 0, 100)]; + nsScroller.translatesAutoresizingMaskIntoConstraints = NO; + nsScroller.enabled = YES; + nsScroller.scrollerStyle = NSScrollerStyleOverlay; + nsScroller.knobStyle = NSScrollerKnobStyleLight; + nsScroller.action = @selector(didScroll:); + nsScroller.target = ssView; + nsScroller.continuous = YES; + + nsContainer = [[NSView alloc] init]; + [nsContainer addSubview:ssView]; + [nsContainer addSubview:nsScroller]; + + NSDictionary *views = NSDictionaryOfVariableBindings(ssView, nsScroller); + nsConstraintsWithoutScrollbar = [NSLayoutConstraint + constraintsWithVisualFormat:@"H:|[ssView]|" + options:0 metrics:nil views:views]; + [nsContainer addConstraints:nsConstraintsWithoutScrollbar]; + nsConstraintsWithScrollbar = [NSLayoutConstraint + constraintsWithVisualFormat:@"H:|[ssView]-0-[nsScroller(11)]|" + options:0 metrics:nil views:views]; + [nsContainer addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"V:|[ssView]|" + options:0 metrics:nil views:views]]; + [nsContainer addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"V:|[nsScroller]|" + options:0 metrics:nil views:views]]; + + switch(kind) { + case Window::Kind::TOPLEVEL: + nsWindow = [[NSWindow alloc] init]; + nsWindow.styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | + NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; + nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary; + ssView.acceptsFirstResponder = YES; + break; + + case Window::Kind::TOOL: + NSPanel *nsPanel = [[NSPanel alloc] init]; + nsPanel.styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | + NSWindowStyleMaskClosable | NSWindowStyleMaskUtilityWindow; + [nsPanel standardWindowButton:NSWindowMiniaturizeButton].hidden = YES; + [nsPanel standardWindowButton:NSWindowZoomButton].hidden = YES; + nsPanel.floatingPanel = YES; + nsPanel.becomesKeyOnlyIfNeeded = YES; + nsWindow = nsPanel; + break; + } + + ssWindowDelegate = [[SSWindowDelegate alloc] init]; + ssWindowDelegate.receiver = this; + nsWindow.delegate = ssWindowDelegate; + + nsWindow.backgroundColor = [NSColor blackColor]; + nsWindow.contentView = nsContainer; + } + + double GetPixelDensity() override { + NSDictionary *description = nsWindow.screen.deviceDescription; + NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; + CGSize displayPhysicalSize = CGDisplayScreenSize( + [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + return (displayPixelSize.width / displayPhysicalSize.width) * 25.4f; + } + + int GetDevicePixelRatio() override { + NSSize unitSize = { 1.0f, 0.0f }; + unitSize = [ssView convertSizeToBacking:unitSize]; + return (int)unitSize.width; + } + + bool IsVisible() override { + return ![nsWindow isVisible]; + } + + void SetVisible(bool visible) override { + if(visible) { + [nsWindow orderFront:nil]; + } else { + [nsWindow close]; + } + } + + void Focus() override { + [nsWindow makeKeyAndOrderFront:nil]; + } + + bool IsFullScreen() override { + return ssWindowDelegate.fullScreen; + } + + void SetFullScreen(bool fullScreen) override { + if(fullScreen != IsFullScreen()) { + [nsWindow toggleFullScreen:nil]; + } + } + + void SetTitle(const std::string &title) override { + nsWindow.representedFilename = @""; + nsWindow.title = Wrap(title); + } + + bool SetTitleForFilename(const Path &filename) override { + [nsWindow setTitleWithRepresentedFilename:Wrap(filename.raw)]; + return true; + } + + void SetMenuBar(MenuBarRef newMenuBar) override { + // Doesn't do anything, since we have an unique global menu bar. + } + + void GetContentSize(double *width, double *height) override { + NSSize nsSize = ssView.frame.size; + *width = nsSize.width; + *height = nsSize.height; + } + + void SetMinContentSize(double width, double height) override { + NSSize nsMinSize; + nsMinSize.width = width; + nsMinSize.height = height; + [nsWindow setContentMinSize:nsMinSize]; + [nsWindow setContentSize:nsMinSize]; + } + + void FreezePosition(SettingsRef _settings, const std::string &key) override { + [nsWindow saveFrameUsingName:Wrap(key)]; + } + + void ThawPosition(SettingsRef _settings, const std::string &key) override { + [nsWindow setFrameUsingName:Wrap(key)]; + } + + void SetCursor(Cursor cursor) override { + NSCursor *nsNewCursor; + switch(cursor) { + case Cursor::POINTER: nsNewCursor = [NSCursor arrowCursor]; break; + case Cursor::HAND: nsNewCursor = [NSCursor pointingHandCursor]; break; + } + + if([NSCursor currentCursor] != nsNewCursor) { + [nsNewCursor set]; + } + } + + void SetTooltip(const std::string &newText, double x, double y, double w, double h) override { + NSString *nsNewText = Wrap(newText); + if(![nsToolTip isEqualToString:nsNewText]) { + nsToolTip = nsNewText; + + NSToolTipManager *nsToolTipManager = [NSToolTipManager sharedToolTipManager]; + if(newText.empty()) { + if ([nsToolTipManager respondsToSelector:@selector(abortToolTip)]) { + [nsToolTipManager abortToolTip]; + } else { + [nsToolTipManager orderOutToolTip]; + } + } else { + [nsToolTipManager _displayTemporaryToolTipForView:ssView withString:Wrap(newText)]; + } + } + } + + bool IsEditorVisible() override { + return [ssView isEditing]; + } + + void ShowEditor(double x, double y, double fontHeight, double minWidth, + bool isMonospace, const std::string &text) override { + [ssView startEditing:Wrap(text) at:(NSPoint){(CGFloat)x, (CGFloat)y} + withHeight:fontHeight minWidth:minWidth usingMonospace:isMonospace]; + } + + void HideEditor() override { + [ssView stopEditing]; + } + + void SetScrollbarVisible(bool visible) override { + if(visible) { + [nsContainer removeConstraints:nsConstraintsWithoutScrollbar]; + [nsContainer addConstraints:nsConstraintsWithScrollbar]; + } else { + [nsContainer removeConstraints:nsConstraintsWithScrollbar]; + [nsContainer addConstraints:nsConstraintsWithoutScrollbar]; + } + } + + void ConfigureScrollbar(double min, double max, double pageSize) override { + ssView.scrollerMin = min; + ssView.scrollerMax = max - pageSize; + [nsScroller setKnobProportion:(pageSize / (ssView.scrollerMax - ssView.scrollerMin))]; + } + + double GetScrollbarPosition() override { + return ssView.scrollerMin + + [nsScroller doubleValue] * (ssView.scrollerMax - ssView.scrollerMin); + } + + void SetScrollbarPosition(double pos) override { + if(pos > ssView.scrollerMax) + pos = ssView.scrollerMax; + if(GetScrollbarPosition() == pos) + return; + [nsScroller setDoubleValue:(pos / (ssView.scrollerMax - ssView.scrollerMin))]; + if(onScrollbarAdjusted) { + onScrollbarAdjusted(pos); + } + } + + void Invalidate() override { + ssView.needsDisplay = YES; + } +}; + +WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { + return std::make_shared(kind, + std::static_pointer_cast(parentWindow)); +} + +//----------------------------------------------------------------------------- +// 3DConnexion support +//----------------------------------------------------------------------------- + +// Normally we would just link to the 3DconnexionClient framework. +// +// We don't want to (are not allowed to) distribute the official framework, so we're trying +// to use the one installed on the users' computer. There are some different versions of +// the framework, the official one and re-implementations using an open source driver for +// older devices (spacenav-plus). So weak-linking isn't an option, either. The only remaining +// way is using CFBundle to dynamically load the library at runtime, and also detect its +// availability. +// +// We're also defining everything needed from the 3DconnexionClientAPI, so we're not depending +// on the API headers. + +#pragma pack(push,2) + +enum { + kConnexionClientModeTakeOver = 1, + kConnexionClientModePlugin = 2 +}; + +#define kConnexionMsgDeviceState '3dSR' +#define kConnexionMaskButtons 0x00FF +#define kConnexionMaskAxis 0x3F00 + +typedef struct { + uint16_t version; + uint16_t client; + uint16_t command; + int16_t param; + int32_t value; + UInt64 time; + uint8_t report[8]; + uint16_t buttons8; + int16_t axis[6]; + uint16_t address; + uint32_t buttons; +} ConnexionDeviceState, *ConnexionDeviceStatePtr; + +#pragma pack(pop) + +typedef void (*ConnexionAddedHandlerProc)(io_connect_t); +typedef void (*ConnexionRemovedHandlerProc)(io_connect_t); +typedef void (*ConnexionMessageHandlerProc)(io_connect_t, natural_t, void *); + +typedef OSErr (*InstallConnexionHandlersProc)(ConnexionMessageHandlerProc, ConnexionAddedHandlerProc, ConnexionRemovedHandlerProc); +typedef void (*CleanupConnexionHandlersProc)(void); +typedef UInt16 (*RegisterConnexionClientProc)(UInt32, UInt8 *, UInt16, UInt32); +typedef void (*UnregisterConnexionClientProc)(UInt16); + +static CFBundleRef spaceBundle = nil; +static InstallConnexionHandlersProc installConnexionHandlers = NULL; +static CleanupConnexionHandlersProc cleanupConnexionHandlers = NULL; +static RegisterConnexionClientProc registerConnexionClient = NULL; +static UnregisterConnexionClientProc unregisterConnexionClient = NULL; +static UInt32 connexionSignature = 'SoSp'; +static UInt8 *connexionName = (UInt8 *)"\x10SolveSpace"; +static UInt16 connexionClient = 0; + +static std::vector> connexionWindows; +static bool connexionShiftIsDown = false; +static bool connexionCommandIsDown = false; + +static void ConnexionAdded(io_connect_t con) {} +static void ConnexionRemoved(io_connect_t con) {} +static void ConnexionMessage(io_connect_t con, natural_t type, void *arg) { + if (type != kConnexionMsgDeviceState) { + return; + } + + ConnexionDeviceState *device = (ConnexionDeviceState *)arg; + + dispatch_async(dispatch_get_main_queue(), ^(void){ + SixDofEvent event = {}; + event.type = SixDofEvent::Type::MOTION; + event.translationX = (double)device->axis[0] * -0.25; + event.translationY = (double)device->axis[1] * -0.25; + event.translationZ = (double)device->axis[2] * 0.25; + event.rotationX = (double)device->axis[3] * -0.0005; + event.rotationY = (double)device->axis[4] * -0.0005; + event.rotationZ = (double)device->axis[5] * -0.0005; + event.shiftDown = connexionShiftIsDown; + event.controlDown = connexionCommandIsDown; + + for(auto window : connexionWindows) { + if(auto windowStrong = window.lock()) { + if(windowStrong->onSixDofEvent) { + windowStrong->onSixDofEvent(event); + } + } + } + }); +} + +void Open3DConnexion() { + NSString *bundlePath = @"/Library/Frameworks/3DconnexionClient.framework"; + NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath]; + spaceBundle = CFBundleCreate(kCFAllocatorDefault, (__bridge CFURLRef)bundleURL); + + // Don't continue if no driver is installed on this machine + if(spaceBundle == nil) { + return; + } + + installConnexionHandlers = (InstallConnexionHandlersProc) + CFBundleGetFunctionPointerForName(spaceBundle, + CFSTR("InstallConnexionHandlers")); + + cleanupConnexionHandlers = (CleanupConnexionHandlersProc) + CFBundleGetFunctionPointerForName(spaceBundle, + CFSTR("CleanupConnexionHandlers")); + + registerConnexionClient = (RegisterConnexionClientProc) + CFBundleGetFunctionPointerForName(spaceBundle, + CFSTR("RegisterConnexionClient")); + + unregisterConnexionClient = (UnregisterConnexionClientProc) + CFBundleGetFunctionPointerForName(spaceBundle, + CFSTR("UnregisterConnexionClient")); + + // Only continue if all required symbols have been loaded + if((installConnexionHandlers == NULL) || (cleanupConnexionHandlers == NULL) + || (registerConnexionClient == NULL) || (unregisterConnexionClient == NULL)) { + CFRelease(spaceBundle); + spaceBundle = nil; + return; + } + + installConnexionHandlers(&ConnexionMessage, &ConnexionAdded, &ConnexionRemoved); + connexionClient = registerConnexionClient(connexionSignature, connexionName, + kConnexionClientModeTakeOver, kConnexionMaskButtons | kConnexionMaskAxis); + + [NSEvent addLocalMonitorForEventsMatchingMask:(NSEventMaskKeyDown | NSEventMaskFlagsChanged) + handler:^(NSEvent *event) { + connexionShiftIsDown = (event.modifierFlags & NSEventModifierFlagShift); + connexionCommandIsDown = (event.modifierFlags & NSEventModifierFlagCommand); + return event; + }]; + + [NSEvent addLocalMonitorForEventsMatchingMask:(NSEventMaskKeyUp | NSEventMaskFlagsChanged) + handler:^(NSEvent *event) { + connexionShiftIsDown = (event.modifierFlags & NSEventModifierFlagShift); + connexionCommandIsDown = (event.modifierFlags & NSEventModifierFlagCommand); + return event; + }]; +} + +void Close3DConnexion() { + if(spaceBundle == nil) { + return; + } + + unregisterConnexionClient(connexionClient); + cleanupConnexionHandlers(); + + CFRelease(spaceBundle); +} + +void Request3DConnexionEventsForWindow(WindowRef window) { + connexionWindows.push_back(window); +} + +//----------------------------------------------------------------------------- +// Message dialogs +//----------------------------------------------------------------------------- + +class MessageDialogImplCocoa final : public MessageDialog { +public: + NSAlert *nsAlert = [[NSAlert alloc] init]; + NSWindow *nsWindow; + + std::vector responses; + + void SetType(Type type) override { + switch(type) { + case Type::INFORMATION: + case Type::QUESTION: + nsAlert.alertStyle = NSAlertStyleInformational; + break; + + case Type::WARNING: + case Type::ERROR: + nsAlert.alertStyle = NSAlertStyleWarning; + break; + } + } + + void SetTitle(std::string title) override { + [nsAlert.window setTitle:Wrap(title)]; + } + + void SetMessage(std::string message) override { + nsAlert.messageText = Wrap(message); + } + + void SetDescription(std::string description) override { + nsAlert.informativeText = Wrap(description); + } + + void AddButton(std::string label, Response response, bool isDefault) override { + NSButton *nsButton = [nsAlert addButtonWithTitle:Wrap(PrepareMnemonics(label))]; + if(!isDefault && [nsButton.keyEquivalent isEqualToString:@"\n"]) { + nsButton.keyEquivalent = @""; + } else if(response == Response::CANCEL) { + nsButton.keyEquivalent = @"\e"; + } + responses.push_back(response); + } + + Response RunModal() override { + // FIXME(platform/gui): figure out a way to run the alert as a sheet + NSModalResponse nsResponse = [nsAlert runModal]; + ssassert(nsResponse >= NSAlertFirstButtonReturn && + nsResponse <= NSAlertFirstButtonReturn + (long)responses.size(), + "Unexpected response"); + return responses[nsResponse - NSAlertFirstButtonReturn]; + } +}; + +MessageDialogRef CreateMessageDialog(WindowRef parentWindow) { + std::shared_ptr dialog = std::make_shared(); + dialog->nsWindow = std::static_pointer_cast(parentWindow)->nsWindow; + return dialog; +} + +//----------------------------------------------------------------------------- +// File dialogs +//----------------------------------------------------------------------------- + +} +} + +@interface SSSaveFormatAccessory : NSViewController +@property NSSavePanel *panel; +@property NSMutableArray *filters; + +@property(nonatomic) NSInteger index; +@property(nonatomic) IBOutlet NSTextField *textField; +@property(nonatomic) IBOutlet NSPopUpButton *button; +@end + +@implementation SSSaveFormatAccessory +@synthesize panel, filters, button; + +- (void)setIndex:(NSInteger)newIndex { + self->_index = newIndex; + NSMutableArray *filter = [filters objectAtIndex:newIndex]; + NSString *extension = [filter objectAtIndex:0]; + if(![extension isEqual:@"*"]) { + NSString *filename = panel.nameFieldStringValue; + NSString *basename = [[filename componentsSeparatedByString:@"."] objectAtIndex:0]; + panel.nameFieldStringValue = [basename stringByAppendingPathExtension:extension]; + } + [panel setAllowedFileTypes:filter]; +} +@end + +namespace SolveSpace { +namespace Platform { + +class FileDialogImplCocoa : public FileDialog { +public: + NSSavePanel *nsPanel = nil; + + void SetTitle(std::string title) override { + nsPanel.title = Wrap(title); + } + + void SetCurrentName(std::string name) override { + nsPanel.nameFieldStringValue = Wrap(name); + } + + Platform::Path GetFilename() override { + return Platform::Path::From(nsPanel.URL.fileSystemRepresentation); + } + + void SetFilename(Platform::Path path) override { + nsPanel.directoryURL = + [NSURL fileURLWithPath:Wrap(path.Parent().raw) isDirectory:YES]; + nsPanel.nameFieldStringValue = Wrap(path.FileStem()); + } + + void FreezeChoices(SettingsRef settings, const std::string &key) override { + settings->FreezeString("Dialog_" + key + "_Folder", + [nsPanel.directoryURL.absoluteString UTF8String]); + } + + void ThawChoices(SettingsRef settings, const std::string &key) override { + nsPanel.directoryURL = + [NSURL URLWithString:Wrap(settings->ThawString("Dialog_" + key + "_Folder", ""))]; + } + + bool RunModal() override { + if([nsPanel runModal] == NSModalResponseOK) { + return true; + } else { + return false; + } + } +}; + +class OpenFileDialogImplCocoa final : public FileDialogImplCocoa { +public: + NSMutableArray *nsFilter = [[NSMutableArray alloc] init]; + + OpenFileDialogImplCocoa() { + SetTitle(C_("title", "Open File")); + } + + void AddFilter(std::string name, std::vector extensions) override { + for(auto extension : extensions) { + [nsFilter addObject:Wrap(extension)]; + } + [nsPanel setAllowedFileTypes:nsFilter]; + } +}; + +class SaveFileDialogImplCocoa final : public FileDialogImplCocoa { +public: + NSMutableArray *nsFilters = [[NSMutableArray alloc] init]; + SSSaveFormatAccessory *ssAccessory = nil; + + SaveFileDialogImplCocoa() { + SetTitle(C_("title", "Save File")); + } + + void AddFilter(std::string name, std::vector extensions) override { + NSMutableArray *nsFilter = [[NSMutableArray alloc] init]; + for(auto extension : extensions) { + [nsFilter addObject:Wrap(extension)]; + } + if(nsFilters.count == 0) { + [nsPanel setAllowedFileTypes:nsFilter]; + } + [nsFilters addObject:nsFilter]; + + std::string desc; + for(auto extension : extensions) { + if(!desc.empty()) desc += ", "; + desc += extension; + } + std::string title = name + " (" + desc + ")"; + if(nsFilters.count == 1) { + [ssAccessory.button removeAllItems]; + } + [ssAccessory.button addItemWithTitle:Wrap(title)]; + [ssAccessory.button synchronizeTitleAndSelectedItem]; + } + + void FreezeChoices(SettingsRef settings, const std::string &key) override { + FileDialogImplCocoa::FreezeChoices(settings, key); + settings->FreezeInt("Dialog_" + key + "_Filter", ssAccessory.index); + } + + void ThawChoices(SettingsRef settings, const std::string &key) override { + FileDialogImplCocoa::ThawChoices(settings, key); + ssAccessory.index = settings->ThawInt("Dialog_" + key + "_Filter", 0); + } + + bool RunModal() override { + if(nsFilters.count == 1) { + nsPanel.accessoryView = nil; + } + + if(nsPanel.nameFieldStringValue.length == 0) { + nsPanel.nameFieldStringValue = Wrap(_("untitled")); + } + + return FileDialogImplCocoa::RunModal(); + } +}; + +FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) { + NSOpenPanel *nsPanel = [NSOpenPanel openPanel]; + nsPanel.canSelectHiddenExtension = YES; + + std::shared_ptr dialog = std::make_shared(); + dialog->nsPanel = nsPanel; + + return dialog; +} + +FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) { + NSSavePanel *nsPanel = [NSSavePanel savePanel]; + nsPanel.canSelectHiddenExtension = YES; + + SSSaveFormatAccessory *ssAccessory = + [[SSSaveFormatAccessory alloc] initWithNibName:@"SaveFormatAccessory" bundle:nil]; + ssAccessory.panel = nsPanel; + nsPanel.accessoryView = [ssAccessory view]; + + std::shared_ptr dialog = std::make_shared(); + dialog->nsPanel = nsPanel; + dialog->ssAccessory = ssAccessory; + ssAccessory.filters = dialog->nsFilters; + + return dialog; +} + +//----------------------------------------------------------------------------- +// Application-wide APIs +//----------------------------------------------------------------------------- + +std::vector GetFontFiles() { + std::vector fonts; + + NSArray *fontNames = [[NSFontManager sharedFontManager] availableFonts]; + for(NSString *fontName in fontNames) { + CTFontDescriptorRef fontRef = + CTFontDescriptorCreateWithNameAndSize ((__bridge CFStringRef)fontName, 10.0); + CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fontRef, kCTFontURLAttribute); + NSString *fontPath = [NSString stringWithString:[(NSURL *)CFBridgingRelease(url) path]]; + fonts.push_back( + Platform::Path::From([[NSFileManager defaultManager] + fileSystemRepresentationWithPath:fontPath])); + } + + return fonts; +} + +void OpenInBrowser(const std::string &url) { + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:Wrap(url)]]; +} + +} +} + +@interface SSApplicationDelegate : NSObject +- (IBAction)preferences:(id)sender; +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename; +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; +@end + +@implementation SSApplicationDelegate +- (IBAction)preferences:(id)sender { + if (!SS.GW.showTextWindow) { + SolveSpace::SS.GW.MenuView(SolveSpace::Command::SHOW_TEXT_WND); + } + SolveSpace::SS.TW.GoToScreen(SolveSpace::TextWindow::Screen::CONFIGURATION); + SolveSpace::SS.ScheduleShowTW(); +} + +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { + SolveSpace::Platform::Path path = SolveSpace::Platform::Path::From([filename UTF8String]); + return SolveSpace::SS.Load(path.Expand(/*fromCurrentDirectory=*/true)); +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { + [[[NSApp mainWindow] delegate] windowShouldClose:[NSApp mainWindow]]; + return NSTerminateCancel; +} + +- (void)applicationTerminatePrompt { + SolveSpace::SS.MenuFile(SolveSpace::Command::EXIT); +} +@end + +namespace SolveSpace { +namespace Platform { + +static SSApplicationDelegate *ssDelegate; + +std::vector InitGui(int argc, char **argv) { + std::vector args = InitCli(argc, argv); + if(args.size() >= 2 && args[1].find("-psn_") == 0) { + // For unknown reasons, Finder passes a Carbon PSN (Process Serial Number) argument + // when a freshly downloaded application is run for the first time. Remove it so + // that it isn't interpreted as a filename. + args.erase(args.begin() + 1); + } + + ssDelegate = [[SSApplicationDelegate alloc] init]; + NSApplication.sharedApplication.delegate = ssDelegate; + + [NSBundle.mainBundle loadNibNamed:@"MainMenu" owner:nil topLevelObjects:nil]; + + NSArray *languages = NSLocale.preferredLanguages; + for(NSString *language in languages) { + if(SolveSpace::SetLocale([language UTF8String])) break; + } + if(languages.count == 0) { + SolveSpace::SetLocale("en_US"); + } + + return args; +} + +void RunGui() { + [NSApp run]; +} + +void ExitGui() { + [NSApp setDelegate:nil]; + [NSApp terminate:nil]; +} + +void ClearGui() {} + +} +} diff --git a/src/platform/guinone.cpp b/src/platform/guinone.cpp new file mode 100644 index 0000000..4641b98 --- /dev/null +++ b/src/platform/guinone.cpp @@ -0,0 +1,145 @@ +//----------------------------------------------------------------------------- +// Our platform support functions for the headless (no OpenGL) test runner. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" +#include + +namespace SolveSpace { + +//----------------------------------------------------------------------------- +// Rendering +//----------------------------------------------------------------------------- + +std::shared_ptr CreateRenderer() { + return std::make_shared(); +} + +namespace Platform { + +//----------------------------------------------------------------------------- +// Fatal errors +//----------------------------------------------------------------------------- + +void FatalError(const std::string &message) { + fprintf(stderr, "%s", message.c_str()); + abort(); +} + +//----------------------------------------------------------------------------- +// Settings +//----------------------------------------------------------------------------- + +class SettingsImplDummy final : public Settings { +public: + void FreezeInt(const std::string &key, uint32_t value) override { + } + + uint32_t ThawInt(const std::string &key, uint32_t defaultValue = 0) override { + return defaultValue; + } + + void FreezeFloat(const std::string &key, double value) override { + } + + double ThawFloat(const std::string &key, double defaultValue = 0.0) override { + return defaultValue; + } + + void FreezeString(const std::string &key, const std::string &value) override { + } + + std::string ThawString(const std::string &key, const std::string &defaultValue = "") override { + return defaultValue; + } +}; + +SettingsRef GetSettings() { + static std::shared_ptr settings = + std::make_shared(); + return settings; +} + +//----------------------------------------------------------------------------- +// Timers +//----------------------------------------------------------------------------- + +class TimerImplDummy final : public Timer { +public: + void RunAfter(unsigned milliseconds) override {} +}; + +TimerRef CreateTimer() { + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// Menus +//----------------------------------------------------------------------------- + +MenuRef CreateMenu() { + return std::shared_ptr(); +} + +MenuBarRef GetOrCreateMainMenu(bool *unique) { + *unique = false; + return std::shared_ptr(); +} + +//----------------------------------------------------------------------------- +// Windows +//----------------------------------------------------------------------------- + +WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { + return std::shared_ptr(); +} + +void Request3DConnexionEventsForWindow(WindowRef window) {} + +//----------------------------------------------------------------------------- +// Message dialogs +//----------------------------------------------------------------------------- + +MessageDialogRef CreateMessageDialog(WindowRef parentWindow) { + return std::shared_ptr(); +} + +//----------------------------------------------------------------------------- +// File dialogs +//----------------------------------------------------------------------------- + +FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) { + return std::shared_ptr(); +} + +FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) { + return std::shared_ptr(); +} + +//----------------------------------------------------------------------------- +// Application-wide APIs +//----------------------------------------------------------------------------- + +std::vector fontFiles; +std::vector GetFontFiles() { + return fontFiles; +} + +void OpenInBrowser(const std::string &url) {} + +std::vector InitGui(int argc, char **argv) { + return {}; +} + +void RunGui() {} + +void ExitGui() { + exit(0); +} + +void ClearGui() {} + +} + +} diff --git a/src/platform/guiwin.cpp b/src/platform/guiwin.cpp new file mode 100644 index 0000000..43e7a59 --- /dev/null +++ b/src/platform/guiwin.cpp @@ -0,0 +1,1690 @@ +//----------------------------------------------------------------------------- +// The Win32-based implementation of platform-dependent GUI functionality. +// +// Copyright 2018 whitequark +//----------------------------------------------------------------------------- +#include "config.h" +#include "solvespace.h" +// Include after solvespace.h to avoid identifier clashes. +#include +#include +#include +#include +#include + +// Macros to compile under XP +#if !defined(LSTATUS) +# define LSTATUS LONG +#endif + +#if !defined(MAPVK_VK_TO_CHAR) +# define MAPVK_VK_TO_CHAR 2 +#endif + +#if !defined(USER_DEFAULT_SCREEN_DPI) +# define USER_DEFAULT_SCREEN_DPI 96 +#endif + +#if !defined(TTM_POPUP) +# define TTM_POPUP (WM_USER + 34) +#endif +// End macros to compile under XP + +#if !defined(WM_DPICHANGED) +# define WM_DPICHANGED 0x02E0 +#endif + +// These interfere with our identifiers. +#undef CreateWindow +#undef ERROR + +#if HAVE_OPENGL == 3 +# define EGLAPI /*static linkage*/ +# define EGL_EGLEXT_PROTOTYPES +# include +# include +#endif + +#if defined(HAVE_SPACEWARE) +# include +# include +# undef uint32_t +#endif + +#if defined(__GNUC__) +// Disable bogus warning emitted by GCC on GetProcAddress, since there seems to be no way +// of restructuring the code to easily disable it just at the call site. +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + +namespace SolveSpace { +namespace Platform { + +//----------------------------------------------------------------------------- +// Windows API bridging +//----------------------------------------------------------------------------- + +#define sscheck(expr) do { \ + SetLastError(0); \ + if(!(expr)) \ + CheckLastError(__FILE__, __LINE__, __func__, #expr); \ + } while(0) + +void CheckLastError(const char *file, int line, const char *function, const char *expr) { + if(GetLastError() != S_OK) { + LPWSTR messageW; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&messageW, 0, NULL); + + std::string message; + message += ssprintf("File %s, line %u, function %s:\n", file, line, function); + message += ssprintf("Win32 API call failed: %s.\n", expr); + message += ssprintf("Error: %s", Narrow(messageW).c_str()); + FatalError(message); + } +} + +typedef UINT (WINAPI *LPFNGETDPIFORWINDOW)(HWND); + +UINT ssGetDpiForWindow(HWND hwnd) { + static bool checked; + static LPFNGETDPIFORWINDOW lpfnGetDpiForWindow; + if(!checked) { + checked = true; + lpfnGetDpiForWindow = (LPFNGETDPIFORWINDOW) + GetProcAddress(GetModuleHandleW(L"user32.dll"), "GetDpiForWindow"); + } + if(lpfnGetDpiForWindow) { + return lpfnGetDpiForWindow(hwnd); + } else { + HDC hDc; + sscheck(hDc = GetDC(HWND_DESKTOP)); + UINT dpi; + sscheck(dpi = GetDeviceCaps(hDc, LOGPIXELSX)); + sscheck(ReleaseDC(HWND_DESKTOP, hDc)); + return dpi; + } +} + +typedef BOOL (WINAPI *LPFNADJUSTWINDOWRECTEXFORDPI)(LPRECT, DWORD, BOOL, DWORD, UINT); + +BOOL ssAdjustWindowRectExForDpi(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, + DWORD dwExStyle, UINT dpi) { + static bool checked; + static LPFNADJUSTWINDOWRECTEXFORDPI lpfnAdjustWindowRectExForDpi; + if(!checked) { + checked = true; + lpfnAdjustWindowRectExForDpi = (LPFNADJUSTWINDOWRECTEXFORDPI) + GetProcAddress(GetModuleHandleW(L"user32.dll"), "AdjustWindowRectExForDpi"); + } + if(lpfnAdjustWindowRectExForDpi) { + return lpfnAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle, dpi); + } else { + return AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle); + } +} + +//----------------------------------------------------------------------------- +// Utility functions +//----------------------------------------------------------------------------- + +static std::wstring PrepareTitle(const std::string &s) { + return Widen("SolveSpace - " + s); +} + +static std::string NegateMnemonics(const std::string &label) { + std::string newLabel; + for(char c : label) { + newLabel.push_back(c); + if(c == '&') newLabel.push_back(c); + } + return newLabel; +} + +static int Clamp(int x, int a, int b) { + return max(a, min(x, b)); +} + +//----------------------------------------------------------------------------- +// Fatal errors +//----------------------------------------------------------------------------- + +bool handlingFatalError = false; + +void FatalError(const std::string &message) { + // Indicate that we're handling a fatal error, to avoid re-entering application code + // and potentially crashing even harder. + handlingFatalError = true; + + switch(MessageBoxW(NULL, Platform::Widen(message + "\nGenerate debug report?").c_str(), + L"Fatal error — SolveSpace", + MB_ICONERROR|MB_TASKMODAL|MB_SETFOREGROUND|MB_TOPMOST| + MB_OKCANCEL|MB_DEFBUTTON2)) { + case IDOK: + abort(); + + case IDCANCEL: + default: { + WCHAR appPath[MAX_PATH] = {}; + GetModuleFileNameW(NULL, appPath, sizeof(appPath)); + ShellExecuteW(NULL, L"open", appPath, NULL, NULL, SW_SHOW); + _exit(1); + } + } +} +//----------------------------------------------------------------------------- +// Settings +//----------------------------------------------------------------------------- + +class SettingsImplWin32 final : public Settings { +public: + HKEY hKey = NULL; + + HKEY GetKey() { + if(hKey == NULL) { + sscheck(RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\SolveSpace", 0, NULL, 0, + KEY_ALL_ACCESS, NULL, &hKey, NULL)); + } + return hKey; + } + + ~SettingsImplWin32() { + if(hKey != NULL) { + sscheck(RegCloseKey(hKey)); + } + } + + void FreezeInt(const std::string &key, uint32_t value) { + sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0, + REG_DWORD, (const BYTE *)&value, sizeof(value))); + } + + uint32_t ThawInt(const std::string &key, uint32_t defaultValue) { + DWORD value; + DWORD type, length = sizeof(value); + LSTATUS result = RegQueryValueExW(GetKey(), &Widen(key)[0], 0, + &type, (BYTE *)&value, &length); + if(result == ERROR_SUCCESS && type == REG_DWORD) { + return value; + } + return defaultValue; + } + + void FreezeFloat(const std::string &key, double value) { + sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0, + REG_QWORD, (const BYTE *)&value, sizeof(value))); + } + + double ThawFloat(const std::string &key, double defaultValue) { + double value; + DWORD type, length = sizeof(value); + LSTATUS result = RegQueryValueExW(GetKey(), &Widen(key)[0], 0, + &type, (BYTE *)&value, &length); + if(result == ERROR_SUCCESS && type == REG_QWORD) { + return value; + } + return defaultValue; + } + + void FreezeString(const std::string &key, const std::string &value) { + ssassert(value.length() == strlen(value.c_str()), + "illegal null byte in middle of a string setting"); + std::wstring valueW = Widen(value); + sscheck(RegSetValueExW(GetKey(), &Widen(key)[0], 0, + REG_SZ, (const BYTE *)&valueW[0], (valueW.length() + 1) * 2)); + } + + std::string ThawString(const std::string &key, const std::string &defaultValue) { + DWORD type, length = 0; + LSTATUS result = RegQueryValueExW(GetKey(), &Widen(key)[0], 0, + &type, NULL, &length); + if(result == ERROR_SUCCESS && type == REG_SZ) { + std::wstring valueW; + valueW.resize(length / 2 - 1); + sscheck(RegQueryValueExW(GetKey(), &Widen(key)[0], 0, + &type, (BYTE *)&valueW[0], &length)); + return Narrow(valueW); + } + return defaultValue; + } +}; + +SettingsRef GetSettings() { + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// Timers +//----------------------------------------------------------------------------- + +class TimerImplWin32 final : public Timer { +public: + static HWND WindowHandle() { + static HWND hTimerWnd; + if(hTimerWnd == NULL) { + sscheck(hTimerWnd = CreateWindowExW(0, L"Message", NULL, 0, 0, 0, 0, 0, + HWND_MESSAGE, NULL, NULL, NULL)); + } + return hTimerWnd; + } + + static void CALLBACK TimerFunc(HWND hwnd, UINT msg, UINT_PTR event, DWORD time) { + sscheck(KillTimer(WindowHandle(), event)); + + TimerImplWin32 *timer = (TimerImplWin32*)event; + if(timer->onTimeout) { + timer->onTimeout(); + } + } + + void RunAfter(unsigned milliseconds) override { + // FIXME(platform/gui): use SetCoalescableTimer when it's available (8+) + sscheck(SetTimer(WindowHandle(), (UINT_PTR)this, + milliseconds, &TimerImplWin32::TimerFunc)); + } + + ~TimerImplWin32() { + // FIXME(platform/gui): there's a race condition here--WM_TIMER messages already + // posted to the queue are not removed, so this destructor is at most "best effort". + KillTimer(WindowHandle(), (UINT_PTR)this); + } +}; + +TimerRef CreateTimer() { + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// Menus +//----------------------------------------------------------------------------- + +class MenuImplWin32; + +class MenuItemImplWin32 final : public MenuItem { +public: + std::shared_ptr menu; + + HMENU Handle(); + + MENUITEMINFOW GetInfo(UINT mask) { + MENUITEMINFOW mii = {}; + mii.cbSize = sizeof(mii); + mii.fMask = mask; + sscheck(GetMenuItemInfoW(Handle(), (UINT_PTR)this, FALSE, &mii)); + return mii; + } + + void SetAccelerator(KeyboardEvent accel) override { + MENUITEMINFOW mii = GetInfo(MIIM_TYPE); + + std::wstring nameW(mii.cch, L'\0'); + mii.dwTypeData = &nameW[0]; + mii.cch++; + sscheck(GetMenuItemInfoW(Handle(), (UINT_PTR)this, FALSE, &mii)); + + std::string name = Narrow(nameW); + if(name.find('\t') != std::string::npos) { + name = name.substr(0, name.find('\t')); + } + name += '\t'; + name += AcceleratorDescription(accel); + + nameW = Widen(name); + mii.fMask = MIIM_STRING; + mii.dwTypeData = &nameW[0]; + sscheck(SetMenuItemInfoW(Handle(), (UINT_PTR)this, FALSE, &mii)); + } + + void SetIndicator(Indicator type) override { + MENUITEMINFOW mii = GetInfo(MIIM_FTYPE); + switch(type) { + case Indicator::NONE: + case Indicator::CHECK_MARK: + mii.fType &= ~MFT_RADIOCHECK; + break; + + case Indicator::RADIO_MARK: + mii.fType |= MFT_RADIOCHECK; + break; + } + sscheck(SetMenuItemInfoW(Handle(), (UINT_PTR)this, FALSE, &mii)); + } + + void SetActive(bool active) override { + MENUITEMINFOW mii = GetInfo(MIIM_STATE); + if(active) { + mii.fState |= MFS_CHECKED; + } else { + mii.fState &= ~MFS_CHECKED; + } + sscheck(SetMenuItemInfoW(Handle(), (UINT_PTR)this, FALSE, &mii)); + } + + void SetEnabled(bool enabled) override { + MENUITEMINFOW mii = GetInfo(MIIM_STATE); + if(enabled) { + mii.fState &= ~(MFS_DISABLED|MFS_GRAYED); + } else { + mii.fState |= MFS_DISABLED|MFS_GRAYED; + } + sscheck(SetMenuItemInfoW(Handle(), (UINT_PTR)this, FALSE, &mii)); + } +}; + +int64_t contextMenuPopTime = 0; + +class MenuImplWin32 final : public Menu { +public: + HMENU hMenu; + + std::weak_ptr weakThis; + std::vector> menuItems; + std::vector> subMenus; + + MenuImplWin32() { + sscheck(hMenu = CreatePopupMenu()); + } + + MenuItemRef AddItem(const std::string &label, + std::function onTrigger = NULL, + bool mnemonics = true) override { + auto menuItem = std::make_shared(); + menuItem->menu = weakThis.lock(); + menuItem->onTrigger = onTrigger; + menuItems.push_back(menuItem); + + sscheck(AppendMenuW(hMenu, MF_STRING, (UINT_PTR)menuItem.get(), + Widen(mnemonics ? label : NegateMnemonics(label)).c_str())); + + // uID is just an UINT, which isn't large enough to hold a pointer on 64-bit Windows, + // so we use dwItemData, which is. + MENUITEMINFOW mii = {}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_DATA; + mii.dwItemData = (LONG_PTR)menuItem.get(); + sscheck(SetMenuItemInfoW(hMenu, (UINT_PTR)menuItem.get(), FALSE, &mii)); + + return menuItem; + } + + MenuRef AddSubMenu(const std::string &label) override { + auto subMenu = std::make_shared(); + subMenu->weakThis = subMenu; + subMenus.push_back(subMenu); + + sscheck(AppendMenuW(hMenu, MF_STRING|MF_POPUP, + (UINT_PTR)subMenu->hMenu, Widen(label).c_str())); + + return subMenu; + } + + void AddSeparator() override { + sscheck(AppendMenuW(hMenu, MF_SEPARATOR, 0, L"")); + } + + void PopUp() override { + MENUINFO mi = {}; + mi.cbSize = sizeof(mi); + mi.fMask = MIM_APPLYTOSUBMENUS|MIM_STYLE; + mi.dwStyle = MNS_NOTIFYBYPOS; + sscheck(SetMenuInfo(hMenu, &mi)); + + POINT pt; + sscheck(GetCursorPos(&pt)); + + sscheck(TrackPopupMenu(hMenu, TPM_TOPALIGN, pt.x, pt.y, 0, GetActiveWindow(), NULL)); + contextMenuPopTime = GetMilliseconds(); + } + + void Clear() override { + for(int n = GetMenuItemCount(hMenu) - 1; n >= 0; n--) { + sscheck(RemoveMenu(hMenu, n, MF_BYPOSITION)); + } + menuItems.clear(); + subMenus.clear(); + } + + ~MenuImplWin32() { + Clear(); + sscheck(DestroyMenu(hMenu)); + } +}; + +HMENU MenuItemImplWin32::Handle() { + return menu->hMenu; +} + +MenuRef CreateMenu() { + auto menu = std::make_shared(); + // std::enable_shared_from_this fails for some reason, not sure why + menu->weakThis = menu; + return menu; +} + +class MenuBarImplWin32 final : public MenuBar { +public: + HMENU hMenuBar; + + std::vector> subMenus; + + MenuBarImplWin32() { + sscheck(hMenuBar = ::CreateMenu()); + } + + MenuRef AddSubMenu(const std::string &label) override { + auto subMenu = std::make_shared(); + subMenu->weakThis = subMenu; + subMenus.push_back(subMenu); + + sscheck(AppendMenuW(hMenuBar, MF_STRING|MF_POPUP, + (UINT_PTR)subMenu->hMenu, Widen(label).c_str())); + + return subMenu; + } + + void Clear() override { + for(int n = GetMenuItemCount(hMenuBar) - 1; n >= 0; n--) { + sscheck(RemoveMenu(hMenuBar, n, MF_BYPOSITION)); + } + subMenus.clear(); + } + + ~MenuBarImplWin32() { + Clear(); + sscheck(DestroyMenu(hMenuBar)); + } +}; + +MenuBarRef GetOrCreateMainMenu(bool *unique) { + *unique = false; + return std::make_shared(); +} + +//----------------------------------------------------------------------------- +// Windows +//----------------------------------------------------------------------------- + +#define SCROLLBAR_UNIT 65536 + +class WindowImplWin32 final : public Window { +public: + HWND hWindow = NULL; + HWND hTooltip = NULL; + HWND hEditor = NULL; + WNDPROC editorWndProc = NULL; + +#if HAVE_OPENGL == 1 + HGLRC hGlRc = NULL; +#elif HAVE_OPENGL == 3 + static EGLDisplay eglDisplay; + EGLSurface eglSurface = EGL_NO_SURFACE; + EGLContext eglContext = EGL_NO_CONTEXT; +#endif + + WINDOWPLACEMENT placement = {}; + int minWidth = 0, minHeight = 0; + +#if defined(HAVE_SPACEWARE) + SiOpenData sod = {}; + SiHdl hSpaceWare = SI_NO_HANDLE; +#endif + + std::shared_ptr menuBar; + std::string tooltipText; + bool scrollbarVisible = false; + + static void RegisterWindowClass() { + static bool registered; + if(registered) return; + + WNDCLASSEXW wc = {}; + wc.cbSize = sizeof(wc); + wc.style = CS_BYTEALIGNCLIENT|CS_BYTEALIGNWINDOW|CS_OWNDC|CS_DBLCLKS; + wc.lpfnWndProc = WndProc; + wc.cbWndExtra = sizeof(WindowImplWin32 *); + wc.hIcon = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(4000), + IMAGE_ICON, 32, 32, 0); + wc.hIconSm = (HICON)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(4000), + IMAGE_ICON, 16, 16, 0); + wc.hCursor = LoadCursorW(NULL, IDC_ARROW); + wc.lpszClassName = L"SolveSpace"; + sscheck(RegisterClassExW(&wc)); + registered = true; + } + + WindowImplWin32(Window::Kind kind, std::shared_ptr parentWindow) { + placement.length = sizeof(placement); + + RegisterWindowClass(); + + HWND hParentWindow = NULL; + if(parentWindow) { + hParentWindow = parentWindow->hWindow; + } + + DWORD style = WS_SIZEBOX|WS_CLIPCHILDREN; + switch(kind) { + case Window::Kind::TOPLEVEL: + style |= WS_OVERLAPPEDWINDOW|WS_CLIPSIBLINGS; + break; + + case Window::Kind::TOOL: + style |= WS_POPUPWINDOW|WS_CAPTION; + break; + } + sscheck(hWindow = CreateWindowExW(0, L"SolveSpace", L"", style, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + hParentWindow, NULL, NULL, NULL)); + sscheck(SetWindowLongPtr(hWindow, 0, (LONG_PTR)this)); + + sscheck(hTooltip = CreateWindowExW(0, TOOLTIPS_CLASS, NULL, + WS_POPUP|TTS_NOPREFIX|TTS_ALWAYSTIP, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + hWindow, NULL, NULL, NULL)); + sscheck(SetWindowPos(hTooltip, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE)); + + TOOLINFOW ti = {}; + ti.cbSize = sizeof(ti); + ti.uFlags = TTF_SUBCLASS; + ti.hwnd = hWindow; + ti.lpszText = (LPWSTR)L""; + sscheck(SendMessageW(hTooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti)); + sscheck(SendMessageW(hTooltip, TTM_ACTIVATE, FALSE, 0)); + + DWORD editorStyle = WS_CLIPSIBLINGS|WS_CHILD|WS_TABSTOP|ES_AUTOHSCROLL; + sscheck(hEditor = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDIT, L"", editorStyle, + 0, 0, 0, 0, hWindow, NULL, NULL, NULL)); + sscheck(editorWndProc = + (WNDPROC)SetWindowLongPtr(hEditor, GWLP_WNDPROC, (LONG_PTR)EditorWndProc)); + + HDC hDc; + sscheck(hDc = GetDC(hWindow)); + +#if HAVE_OPENGL == 1 + PIXELFORMATDESCRIPTOR pfd = {}; + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER; + pfd.dwLayerMask = PFD_MAIN_PLANE; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + pfd.cDepthBits = 24; + pfd.cAccumBits = 0; + pfd.cStencilBits = 0; + int pixelFormat; + sscheck(pixelFormat = ChoosePixelFormat(hDc, &pfd)); + sscheck(SetPixelFormat(hDc, pixelFormat, &pfd)); + + sscheck(hGlRc = wglCreateContext(hDc)); +#elif HAVE_OPENGL == 3 + if(eglDisplay == EGL_NO_DISPLAY) { + ssassert(eglBindAPI(EGL_OPENGL_ES_API), "Cannot bind EGL API"); + + EGLBoolean initialized = EGL_FALSE; + for(auto &platformType : { + // Try platform types from least to most amount of software translation required. + std::make_pair("OpenGL ES", EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE), + std::make_pair("OpenGL", EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE), + std::make_pair("Direct3D 11", EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE), + std::make_pair("Direct3D 9", EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE), + }) { + dbp("Initializing ANGLE with %s backend", platformType.first); + EGLint displayAttributes[] = { + EGL_PLATFORM_ANGLE_TYPE_ANGLE, platformType.second, + EGL_NONE + }; + eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, hDc, + displayAttributes); + if(eglDisplay != EGL_NO_DISPLAY) { + initialized = eglInitialize(eglDisplay, NULL, NULL); + if(initialized) break; + eglTerminate(eglDisplay); + } + } + ssassert(initialized, "Cannot find a suitable EGL display"); + } + + EGLint configAttributes[] = { + EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_NONE + }; + EGLint numConfigs; + EGLConfig windowConfig; + ssassert(eglChooseConfig(eglDisplay, configAttributes, &windowConfig, 1, &numConfigs), + "Cannot choose EGL configuration"); + + EGLint surfaceAttributes[] = { + EGL_NONE + }; + eglSurface = eglCreateWindowSurface(eglDisplay, windowConfig, hWindow, surfaceAttributes); + ssassert(eglSurface != EGL_NO_SURFACE, "Cannot create EGL window surface"); + + EGLint contextAttributes[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + eglContext = eglCreateContext(eglDisplay, windowConfig, NULL, contextAttributes); + ssassert(eglContext != EGL_NO_CONTEXT, "Cannot create EGL context"); +#endif + + sscheck(ReleaseDC(hWindow, hDc)); + } + + ~WindowImplWin32() { + // Make sure any of our child windows get destroyed before we call DestroyWindow, or their + // own destructors may fail. + menuBar.reset(); + + sscheck(DestroyWindow(hWindow)); +#if defined(HAVE_SPACEWARE) + if(hSpaceWare != SI_NO_HANDLE) { + SiClose(hSpaceWare); + } +#endif + } + + static LRESULT CALLBACK WndProc(HWND h, UINT msg, WPARAM wParam, LPARAM lParam) { + if(handlingFatalError) return TRUE; + + WindowImplWin32 *window; + sscheck(window = (WindowImplWin32 *)GetWindowLongPtr(h, 0)); + + // The wndproc may be called from within CreateWindowEx, and before we've associated + // the window with the WindowImplWin32. In that case, just defer to the default wndproc. + if(window == NULL) { + return DefWindowProcW(h, msg, wParam, lParam); + } + +#if defined(HAVE_SPACEWARE) + if(window->hSpaceWare != SI_NO_HANDLE) { + SiGetEventData sged; + SiGetEventWinInit(&sged, msg, wParam, lParam); + + SiSpwEvent sse; + if(SiGetEvent(window->hSpaceWare, 0, &sged, &sse) == SI_IS_EVENT) { + SixDofEvent event = {}; + event.shiftDown = ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0); + event.controlDown = ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0); + if(sse.type == SI_MOTION_EVENT) { + // The Z axis translation and rotation are both + // backwards in the default mapping. + event.type = SixDofEvent::Type::MOTION; + event.translationX = sse.u.spwData.mData[SI_TX]*1.0, + event.translationY = sse.u.spwData.mData[SI_TY]*1.0, + event.translationZ = -sse.u.spwData.mData[SI_TZ]*1.0, + event.rotationX = sse.u.spwData.mData[SI_RX]*0.001, + event.rotationY = sse.u.spwData.mData[SI_RY]*0.001, + event.rotationZ = -sse.u.spwData.mData[SI_RZ]*0.001; + } else if(sse.type == SI_BUTTON_EVENT) { + if(SiButtonPressed(&sse) == SI_APP_FIT_BUTTON) { + event.type = SixDofEvent::Type::PRESS; + event.button = SixDofEvent::Button::FIT; + } + if(SiButtonReleased(&sse) == SI_APP_FIT_BUTTON) { + event.type = SixDofEvent::Type::RELEASE; + event.button = SixDofEvent::Button::FIT; + } + } + return 0; + } + } +#endif + + switch (msg) { + case WM_ERASEBKGND: + break; + + case WM_PAINT: { + PAINTSTRUCT ps; + HDC hDc = BeginPaint(window->hWindow, &ps); + if(window->onRender) { +#if HAVE_OPENGL == 1 + wglMakeCurrent(hDc, window->hGlRc); +#elif HAVE_OPENGL == 3 + eglMakeCurrent(window->eglDisplay, window->eglSurface, + window->eglSurface, window->eglContext); +#endif + window->onRender(); +#if HAVE_OPENGL == 1 + SwapBuffers(hDc); +#elif HAVE_OPENGL == 3 + eglSwapBuffers(window->eglDisplay, window->eglSurface); + (void)hDc; +#endif + } + EndPaint(window->hWindow, &ps); + break; + } + + case WM_CLOSE: + if(window->onClose) { + window->onClose(); + } + break; + + case WM_SIZE: + window->Invalidate(); + break; + + case WM_SIZING: { + int pixelRatio = window->GetDevicePixelRatio(); + + RECT rcw, rcc; + sscheck(GetWindowRect(window->hWindow, &rcw)); + sscheck(GetClientRect(window->hWindow, &rcc)); + int nonClientWidth = (rcw.right - rcw.left) - (rcc.right - rcc.left); + int nonClientHeight = (rcw.bottom - rcw.top) - (rcc.bottom - rcc.top); + + RECT *rc = (RECT *)lParam; + int adjWidth = rc->right - rc->left; + int adjHeight = rc->bottom - rc->top; + + adjWidth -= nonClientWidth; + adjWidth = max(window->minWidth * pixelRatio, adjWidth); + adjWidth += nonClientWidth; + adjHeight -= nonClientHeight; + adjHeight = max(window->minHeight * pixelRatio, adjHeight); + adjHeight += nonClientHeight; + switch(wParam) { + case WMSZ_RIGHT: + case WMSZ_BOTTOMRIGHT: + case WMSZ_TOPRIGHT: + rc->right = rc->left + adjWidth; + break; + + case WMSZ_LEFT: + case WMSZ_BOTTOMLEFT: + case WMSZ_TOPLEFT: + rc->left = rc->right - adjWidth; + break; + } + switch(wParam) { + case WMSZ_BOTTOM: + case WMSZ_BOTTOMLEFT: + case WMSZ_BOTTOMRIGHT: + rc->bottom = rc->top + adjHeight; + break; + + case WMSZ_TOP: + case WMSZ_TOPLEFT: + case WMSZ_TOPRIGHT: + rc->top = rc->bottom - adjHeight; + break; + } + break; + } + + case WM_DPICHANGED: { + RECT rc = *(RECT *)lParam; + sscheck(SendMessage(window->hWindow, WM_SIZING, WMSZ_BOTTOMRIGHT, (LPARAM)&rc)); + sscheck(SetWindowPos(window->hWindow, NULL, rc.left, rc.top, + rc.right - rc.left, rc.bottom - rc.top, + SWP_NOZORDER|SWP_NOACTIVATE)); + window->Invalidate(); + break; + } + + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + if(GetMilliseconds() - Platform::contextMenuPopTime < 100) { + // Ignore the mouse click that dismisses a context menu, to avoid + // (e.g.) clearing a selection. + return 0; + } + // fallthrough + case WM_MOUSEMOVE: + case WM_MOUSEWHEEL: + case WM_MOUSELEAVE: { + int pixelRatio = window->GetDevicePixelRatio(); + + MouseEvent event = {}; + event.x = GET_X_LPARAM(lParam) / pixelRatio; + event.y = GET_Y_LPARAM(lParam) / pixelRatio; + event.button = MouseEvent::Button::NONE; + + event.shiftDown = (wParam & MK_SHIFT) != 0; + event.controlDown = (wParam & MK_CONTROL) != 0; + + bool consumed = false; + switch(msg) { + case WM_LBUTTONDOWN: + event.button = MouseEvent::Button::LEFT; + event.type = MouseEvent::Type::PRESS; + break; + case WM_MBUTTONDOWN: + event.button = MouseEvent::Button::MIDDLE; + event.type = MouseEvent::Type::PRESS; + break; + case WM_RBUTTONDOWN: + event.button = MouseEvent::Button::RIGHT; + event.type = MouseEvent::Type::PRESS; + break; + + case WM_LBUTTONDBLCLK: + event.button = MouseEvent::Button::LEFT; + event.type = MouseEvent::Type::DBL_PRESS; + break; + case WM_MBUTTONDBLCLK: + event.button = MouseEvent::Button::MIDDLE; + event.type = MouseEvent::Type::DBL_PRESS; + break; + case WM_RBUTTONDBLCLK: + event.button = MouseEvent::Button::RIGHT; + event.type = MouseEvent::Type::DBL_PRESS; + break; + + case WM_LBUTTONUP: + event.button = MouseEvent::Button::LEFT; + event.type = MouseEvent::Type::RELEASE; + break; + case WM_MBUTTONUP: + event.button = MouseEvent::Button::MIDDLE; + event.type = MouseEvent::Type::RELEASE; + break; + case WM_RBUTTONUP: + event.button = MouseEvent::Button::RIGHT; + event.type = MouseEvent::Type::RELEASE; + break; + + case WM_MOUSEWHEEL: + // Make the mousewheel work according to which window the mouse is + // over, not according to which window is active. + POINT pt; + pt.x = LOWORD(lParam); + pt.y = HIWORD(lParam); + HWND hWindowUnderMouse; + sscheck(hWindowUnderMouse = WindowFromPoint(pt)); + if(hWindowUnderMouse && hWindowUnderMouse != h) { + SendMessageW(hWindowUnderMouse, msg, wParam, lParam); + consumed = true; + break; + } + + event.type = MouseEvent::Type::SCROLL_VERT; + event.scrollDelta = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? 1 : -1; + break; + + case WM_MOUSELEAVE: + event.type = MouseEvent::Type::LEAVE; + break; + case WM_MOUSEMOVE: { + event.type = MouseEvent::Type::MOTION; + + if(wParam & MK_LBUTTON) { + event.button = MouseEvent::Button::LEFT; + } else if(wParam & MK_MBUTTON) { + event.button = MouseEvent::Button::MIDDLE; + } else if(wParam & MK_RBUTTON) { + event.button = MouseEvent::Button::RIGHT; + } + + // We need this in order to get the WM_MOUSELEAVE + TRACKMOUSEEVENT tme = {}; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = window->hWindow; + sscheck(TrackMouseEvent(&tme)); + break; + } + } + + if(!consumed && window->onMouseEvent) { + window->onMouseEvent(event); + } + break; + } + + case WM_KEYDOWN: + case WM_KEYUP: { + Platform::KeyboardEvent event = {}; + if(msg == WM_KEYDOWN) { + event.type = Platform::KeyboardEvent::Type::PRESS; + } else if(msg == WM_KEYUP) { + event.type = Platform::KeyboardEvent::Type::RELEASE; + } + + if(GetKeyState(VK_SHIFT) & 0x8000) + event.shiftDown = true; + if(GetKeyState(VK_CONTROL) & 0x8000) + event.controlDown = true; + + if(wParam >= VK_F1 && wParam <= VK_F12) { + event.key = Platform::KeyboardEvent::Key::FUNCTION; + event.num = wParam - VK_F1 + 1; + } else { + event.key = Platform::KeyboardEvent::Key::CHARACTER; + event.chr = tolower(MapVirtualKeyW(wParam, MAPVK_VK_TO_CHAR)); + if(event.chr == 0) { + if(wParam == VK_DELETE) { + event.chr = '\x7f'; + } else { + // Non-mappable key. + break; + } + } else if(event.chr == '.' && event.shiftDown) { + event.chr = '>'; + event.shiftDown = false;; + } + } + + if(window->onKeyboardEvent) { + window->onKeyboardEvent(event); + } + break; + } + + case WM_SYSKEYDOWN: { + HWND hParent; + sscheck(hParent = GetParent(h)); + if(hParent != NULL) { + // If the user presses the Alt key when a tool window has focus, + // then that should probably go to the main window instead. + sscheck(SetForegroundWindow(hParent)); + break; + } else { + return DefWindowProcW(h, msg, wParam, lParam); + } + } + + case WM_VSCROLL: { + SCROLLINFO si = {}; + si.cbSize = sizeof(si); + si.fMask = SIF_POS|SIF_TRACKPOS|SIF_RANGE|SIF_PAGE; + sscheck(GetScrollInfo(window->hWindow, SB_VERT, &si)); + + switch(LOWORD(wParam)) { + case SB_LINEUP: si.nPos -= SCROLLBAR_UNIT; break; + case SB_PAGEUP: si.nPos -= si.nPage; break; + case SB_LINEDOWN: si.nPos += SCROLLBAR_UNIT; break; + case SB_PAGEDOWN: si.nPos += si.nPage; break; + case SB_TOP: si.nPos = si.nMin; break; + case SB_BOTTOM: si.nPos = si.nMax; break; + case SB_THUMBTRACK: + case SB_THUMBPOSITION: si.nPos = si.nTrackPos; break; + } + + si.nPos = min((UINT)si.nPos, (UINT)(si.nMax - si.nPage)); + + if(window->onScrollbarAdjusted) { + window->onScrollbarAdjusted((double)si.nPos / SCROLLBAR_UNIT); + } + break; + } + + case WM_MENUCOMMAND: { + MENUITEMINFOW mii = {}; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_DATA; + sscheck(GetMenuItemInfoW((HMENU)lParam, wParam, TRUE, &mii)); + + MenuItemImplWin32 *menuItem = (MenuItemImplWin32 *)mii.dwItemData; + if(menuItem->onTrigger) { + menuItem->onTrigger(); + } + break; + } + + default: + return DefWindowProcW(h, msg, wParam, lParam); + } + + return 0; + } + + static LRESULT CALLBACK EditorWndProc(HWND h, UINT msg, WPARAM wParam, LPARAM lParam) { + if(handlingFatalError) return 0; + + HWND hWindow; + sscheck(hWindow = GetParent(h)); + + WindowImplWin32 *window; + sscheck(window = (WindowImplWin32 *)GetWindowLongPtr(hWindow, 0)); + + switch(msg) { + case WM_CHAR: + if(wParam == VK_RETURN) { + if(window->onEditingDone) { + int length; + sscheck(length = GetWindowTextLength(h)); + + std::wstring resultW; + resultW.resize(length); + sscheck(GetWindowTextW(h, &resultW[0], resultW.length() + 1)); + + window->onEditingDone(Narrow(resultW)); + return 0; + } + } else if(wParam == VK_ESCAPE) { + sscheck(SendMessageW(hWindow, msg, wParam, lParam)); + return 0; + } + } + + return CallWindowProc(window->editorWndProc, h, msg, wParam, lParam); + } + + double GetPixelDensity() override { + UINT dpi; + sscheck(dpi = ssGetDpiForWindow(hWindow)); + return (double)dpi; + } + + int GetDevicePixelRatio() override { + UINT dpi; + sscheck(dpi = ssGetDpiForWindow(hWindow)); + return dpi / USER_DEFAULT_SCREEN_DPI; + } + + bool IsVisible() override { + BOOL isVisible; + sscheck(isVisible = IsWindowVisible(hWindow)); + return isVisible == TRUE; + } + + void SetVisible(bool visible) override { + sscheck(ShowWindow(hWindow, visible ? SW_SHOW : SW_HIDE)); + } + + void Focus() override { + sscheck(SetActiveWindow(hWindow)); + } + + bool IsFullScreen() override { + DWORD style; + sscheck(style = GetWindowLongPtr(hWindow, GWL_STYLE)); + return !(style & WS_OVERLAPPEDWINDOW); + } + + void SetFullScreen(bool fullScreen) override { + DWORD style; + sscheck(style = GetWindowLongPtr(hWindow, GWL_STYLE)); + if(fullScreen) { + sscheck(GetWindowPlacement(hWindow, &placement)); + + MONITORINFO mi; + mi.cbSize = sizeof(mi); + sscheck(GetMonitorInfo(MonitorFromWindow(hWindow, MONITOR_DEFAULTTONEAREST), &mi)); + + sscheck(SetWindowLong(hWindow, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW)); + sscheck(SetWindowPos(hWindow, HWND_TOP, + mi.rcMonitor.left, mi.rcMonitor.top, + mi.rcMonitor.right - mi.rcMonitor.left, + mi.rcMonitor.bottom - mi.rcMonitor.top, + SWP_NOOWNERZORDER|SWP_FRAMECHANGED)); + } else { + sscheck(SetWindowLong(hWindow, GWL_STYLE, style | WS_OVERLAPPEDWINDOW)); + sscheck(SetWindowPlacement(hWindow, &placement)); + sscheck(SetWindowPos(hWindow, NULL, 0, 0, 0, 0, + SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER| + SWP_NOOWNERZORDER|SWP_FRAMECHANGED)); + } + } + + void SetTitle(const std::string &title) override { + sscheck(SetWindowTextW(hWindow, PrepareTitle(title).c_str())); + } + + void SetMenuBar(MenuBarRef newMenuBar) override { + menuBar = std::static_pointer_cast(newMenuBar); + + MENUINFO mi = {}; + mi.cbSize = sizeof(mi); + mi.fMask = MIM_APPLYTOSUBMENUS|MIM_STYLE; + mi.dwStyle = MNS_NOTIFYBYPOS; + sscheck(SetMenuInfo(menuBar->hMenuBar, &mi)); + + sscheck(SetMenu(hWindow, menuBar->hMenuBar)); + } + + void GetContentSize(double *width, double *height) override { + int pixelRatio = GetDevicePixelRatio(); + + RECT rc; + sscheck(GetClientRect(hWindow, &rc)); + *width = (rc.right - rc.left) / pixelRatio; + *height = (rc.bottom - rc.top) / pixelRatio; + } + + void SetMinContentSize(double width, double height) { + minWidth = (int)width; + minHeight = (int)height; + + int pixelRatio = GetDevicePixelRatio(); + + RECT rc; + sscheck(GetClientRect(hWindow, &rc)); + if(rc.right - rc.left < minWidth * pixelRatio) { + rc.right = rc.left + minWidth * pixelRatio; + } + if(rc.bottom - rc.top < minHeight * pixelRatio) { + rc.bottom = rc.top + minHeight * pixelRatio; + } + } + + void FreezePosition(SettingsRef settings, const std::string &key) override { + sscheck(GetWindowPlacement(hWindow, &placement)); + + BOOL isMaximized; + sscheck(isMaximized = IsZoomed(hWindow)); + + RECT rc = placement.rcNormalPosition; + settings->FreezeInt(key + "_Left", rc.left); + settings->FreezeInt(key + "_Right", rc.right); + settings->FreezeInt(key + "_Top", rc.top); + settings->FreezeInt(key + "_Bottom", rc.bottom); + settings->FreezeBool(key + "_Maximized", isMaximized == TRUE); + } + + void ThawPosition(SettingsRef settings, const std::string &key) override { + sscheck(GetWindowPlacement(hWindow, &placement)); + + RECT rc = placement.rcNormalPosition; + rc.left = settings->ThawInt(key + "_Left", rc.left); + rc.right = settings->ThawInt(key + "_Right", rc.right); + rc.top = settings->ThawInt(key + "_Top", rc.top); + rc.bottom = settings->ThawInt(key + "_Bottom", rc.bottom); + + MONITORINFO mi; + mi.cbSize = sizeof(mi); + sscheck(GetMonitorInfo(MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST), &mi)); + + // If it somehow ended up off-screen, then put it back. + RECT mrc = mi.rcMonitor; + rc.left = Clamp(rc.left, mrc.left, mrc.right); + rc.right = Clamp(rc.right, mrc.left, mrc.right); + rc.top = Clamp(rc.top, mrc.top, mrc.bottom); + rc.bottom = Clamp(rc.bottom, mrc.top, mrc.bottom); + + // And make sure the minimum size is respected. (We can freeze a size smaller + // than minimum size if the DPI changed between runs.) + sscheck(SendMessageW(hWindow, WM_SIZING, WMSZ_BOTTOMRIGHT, (LPARAM)&rc)); + + placement.flags = 0; + if(settings->ThawBool(key + "_Maximized", false)) { + placement.showCmd = SW_SHOWMAXIMIZED; + } else { + placement.showCmd = SW_SHOW; + } + placement.rcNormalPosition = rc; + sscheck(SetWindowPlacement(hWindow, &placement)); + } + + void SetCursor(Cursor cursor) override { + LPWSTR cursorName = IDC_ARROW; + switch(cursor) { + case Cursor::POINTER: cursorName = IDC_ARROW; break; + case Cursor::HAND: cursorName = IDC_HAND; break; + } + + HCURSOR hCursor; + sscheck(hCursor = LoadCursorW(NULL, cursorName)); + sscheck(::SetCursor(hCursor)); + } + + void SetTooltip(const std::string &newText, double x, double y, + double width, double height) override { + if(newText == tooltipText) return; + tooltipText = newText; + + if(!newText.empty()) { + int pixelRatio = GetDevicePixelRatio(); + RECT toolRect; + toolRect.left = (int)(x * pixelRatio); + toolRect.top = (int)(y * pixelRatio); + toolRect.right = toolRect.left + (int)(width * pixelRatio); + toolRect.bottom = toolRect.top + (int)(height * pixelRatio); + + std::wstring newTextW = Widen(newText); + TOOLINFOW ti = {}; + ti.cbSize = sizeof(ti); + ti.hwnd = hWindow; + ti.rect = toolRect; + ti.lpszText = &newTextW[0]; + sscheck(SendMessageW(hTooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti)); + sscheck(SendMessageW(hTooltip, TTM_NEWTOOLRECTW, 0, (LPARAM)&ti)); + } + // The following SendMessage call sometimes fails with ERROR_ACCESS_DENIED for + // no discernible reason, but only on wine. + SendMessageW(hTooltip, TTM_ACTIVATE, !newText.empty(), 0); + } + + bool IsEditorVisible() override { + BOOL visible; + sscheck(visible = IsWindowVisible(hEditor)); + return visible == TRUE; + } + + void ShowEditor(double x, double y, double fontHeight, double minWidth, + bool isMonospace, const std::string &text) override { + if(IsEditorVisible()) return; + + int pixelRatio = GetDevicePixelRatio(); + + HFONT hFont = CreateFontW(-(LONG)fontHeight * GetDevicePixelRatio(), 0, 0, 0, + FW_REGULAR, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, FF_DONTCARE, isMonospace ? L"Lucida Console" : L"Arial"); + if(hFont == NULL) { + sscheck(hFont = (HFONT)GetStockObject(SYSTEM_FONT)); + } + sscheck(SendMessageW(hEditor, WM_SETFONT, (WPARAM)hFont, FALSE)); + sscheck(SendMessageW(hEditor, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, 0)); + + std::wstring textW = Widen(text); + + HDC hDc; + TEXTMETRICW tm; + SIZE ts; + sscheck(hDc = GetDC(hEditor)); + sscheck(SelectObject(hDc, hFont)); + sscheck(GetTextMetricsW(hDc, &tm)); + sscheck(GetTextExtentPoint32W(hDc, textW.c_str(), textW.length(), &ts)); + sscheck(ReleaseDC(hEditor, hDc)); + + RECT rc; + rc.left = (LONG)x * pixelRatio; + rc.top = (LONG)y * pixelRatio - tm.tmAscent; + // Add one extra char width to avoid scrolling. + rc.right = (LONG)x * pixelRatio + + std::max((LONG)minWidth * pixelRatio, ts.cx + tm.tmAveCharWidth); + rc.bottom = (LONG)y * pixelRatio + tm.tmDescent; + sscheck(ssAdjustWindowRectExForDpi(&rc, 0, /*bMenu=*/FALSE, WS_EX_CLIENTEDGE, + ssGetDpiForWindow(hWindow))); + + sscheck(MoveWindow(hEditor, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, + /*bRepaint=*/true)); + sscheck(ShowWindow(hEditor, SW_SHOW)); + if(!textW.empty()) { + sscheck(SendMessageW(hEditor, WM_SETTEXT, 0, (LPARAM)textW.c_str())); + sscheck(SendMessageW(hEditor, EM_SETSEL, 0, textW.length())); + sscheck(SetFocus(hEditor)); + } + } + + void HideEditor() override { + if(!IsEditorVisible()) return; + + sscheck(ShowWindow(hEditor, SW_HIDE)); + } + + void SetScrollbarVisible(bool visible) override { + scrollbarVisible = visible; + sscheck(ShowScrollBar(hWindow, SB_VERT, visible)); + } + + void ConfigureScrollbar(double min, double max, double pageSize) override { + SCROLLINFO si = {}; + si.cbSize = sizeof(si); + si.fMask = SIF_RANGE|SIF_PAGE; + si.nMin = (UINT)(min * SCROLLBAR_UNIT); + si.nMax = (UINT)(max * SCROLLBAR_UNIT); + si.nPage = (UINT)(pageSize * SCROLLBAR_UNIT); + sscheck(SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE)); + } + + double GetScrollbarPosition() override { + if(!scrollbarVisible) return 0.0; + + SCROLLINFO si = {}; + si.cbSize = sizeof(si); + si.fMask = SIF_POS; + sscheck(GetScrollInfo(hWindow, SB_VERT, &si)); + return (double)si.nPos / SCROLLBAR_UNIT; + } + + void SetScrollbarPosition(double pos) override { + if(!scrollbarVisible) return; + + SCROLLINFO si = {}; + si.cbSize = sizeof(si); + si.fMask = SIF_POS; + sscheck(GetScrollInfo(hWindow, SB_VERT, &si)); + if(si.nPos == (int)(pos * SCROLLBAR_UNIT)) + return; + + si.nPos = (int)(pos * SCROLLBAR_UNIT); + sscheck(SetScrollInfo(hWindow, SB_VERT, &si, /*redraw=*/TRUE)); + + // Windows won't synthesize a WM_VSCROLL for us here. + if(onScrollbarAdjusted) { + onScrollbarAdjusted((double)si.nPos / SCROLLBAR_UNIT); + } + } + + void Invalidate() override { + sscheck(InvalidateRect(hWindow, NULL, /*bErase=*/FALSE)); + } +}; + +#if HAVE_OPENGL == 3 +EGLDisplay WindowImplWin32::eglDisplay = EGL_NO_DISPLAY; +#endif + +WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) { + return std::make_shared(kind, + std::static_pointer_cast(parentWindow)); +} + +//----------------------------------------------------------------------------- +// 3DConnexion support +//----------------------------------------------------------------------------- + +#if defined(HAVE_SPACEWARE) +static HWND hSpaceWareDriverClass; + +void Open3DConnexion() { + hSpaceWareDriverClass = FindWindowW(L"SpaceWare Driver Class", NULL); + if(hSpaceWareDriverClass != NULL) { + SiInitialize(); + } +} + +void Close3DConnexion() { + if(hSpaceWareDriverClass != NULL) { + SiTerminate(); + } +} + +void Request3DConnexionEventsForWindow(WindowRef window) { + std::shared_ptr windowImpl = + std::static_pointer_cast(window); + if(hSpaceWareDriverClass != NULL) { + SiOpenWinInit(&windowImpl->sod, windowImpl->hWindow); + windowImpl->hSpaceWare = SiOpen("SolveSpace", SI_ANY_DEVICE, SI_NO_MASK, SI_EVENT, + &windowImpl->sod); + SiSetUiMode(windowImpl->hSpaceWare, SI_UI_NO_CONTROLS); + } +} +#else +void Open3DConnexion() {} +void Close3DConnexion() {} +void Request3DConnexionEventsForWindow(WindowRef window) {} +#endif + +//----------------------------------------------------------------------------- +// Message dialogs +//----------------------------------------------------------------------------- + +class MessageDialogImplWin32 final : public MessageDialog { +public: + MSGBOXPARAMSW mbp = {}; + + int style; + + std::wstring titleW; + std::wstring messageW; + std::wstring descriptionW; + std::wstring textW; + + std::vector buttons; + int defaultButton; + + MessageDialogImplWin32() { + mbp.cbSize = sizeof(mbp); + SetTitle("Message"); + } + + void SetType(Type type) override { + switch(type) { + case Type::INFORMATION: + style = MB_ICONINFORMATION; + break; + + case Type::QUESTION: + style = MB_ICONQUESTION; + break; + + case Type::WARNING: + style = MB_ICONWARNING; + break; + + case Type::ERROR: + style = MB_ICONERROR; + break; + } + } + + void SetTitle(std::string title) override { + titleW = PrepareTitle(title); + mbp.lpszCaption = titleW.c_str(); + } + + void SetMessage(std::string message) override { + messageW = Widen(message); + UpdateText(); + } + + void SetDescription(std::string description) override { + descriptionW = Widen(description); + UpdateText(); + } + + void UpdateText() { + textW = messageW + L"\n\n" + descriptionW; + mbp.lpszText = textW.c_str(); + } + + void AddButton(std::string _label, Response response, bool isDefault) override { + int button; + switch(response) { + case Response::NONE: ssassert(false, "Invalid response"); + case Response::OK: button = IDOK; break; + case Response::YES: button = IDYES; break; + case Response::NO: button = IDNO; break; + case Response::CANCEL: button = IDCANCEL; break; + } + buttons.push_back(button); + if(isDefault) { + defaultButton = button; + } + } + + Response RunModal() override { + mbp.dwStyle = style; + + std::sort(buttons.begin(), buttons.end()); + if(buttons == std::vector({ IDOK })) { + mbp.dwStyle |= MB_OK; + } else if(buttons == std::vector({ IDOK, IDCANCEL })) { + mbp.dwStyle |= MB_OKCANCEL; + } else if(buttons == std::vector({ IDYES, IDNO })) { + mbp.dwStyle |= MB_YESNO; + } else if(buttons == std::vector({ IDCANCEL, IDYES, IDNO })) { + mbp.dwStyle |= MB_YESNOCANCEL; + } else { + ssassert(false, "Unexpected button set"); + } + + switch(MessageBoxIndirectW(&mbp)) { + case IDOK: return Response::OK; break; + case IDYES: return Response::YES; break; + case IDNO: return Response::NO; break; + case IDCANCEL: return Response::CANCEL; break; + default: ssassert(false, "Unexpected response"); + } + } +}; + +MessageDialogRef CreateMessageDialog(WindowRef parentWindow) { + std::shared_ptr dialog = std::make_shared(); + dialog->mbp.hwndOwner = std::static_pointer_cast(parentWindow)->hWindow; + return dialog; +} + +//----------------------------------------------------------------------------- +// File dialogs +//----------------------------------------------------------------------------- + +class FileDialogImplWin32 final : public FileDialog { +public: + OPENFILENAMEW ofn = {}; + bool isSaveDialog; + std::wstring titleW; + std::wstring filtersW; + std::wstring defExtW; + std::wstring initialDirW; + // UNC paths may be as long as 32767 characters. + // Unfortunately, the Get*FileName API does not provide any way to use it + // except with a preallocated buffer of fixed size, so we use something + // reasonably large. + wchar_t filenameWC[32768] = {}; + + FileDialogImplWin32() { + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFile = filenameWC; + ofn.nMaxFile = sizeof(filenameWC) / sizeof(wchar_t); + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | + OFN_OVERWRITEPROMPT; + if(isSaveDialog) { + SetTitle(C_("title", "Save File")); + } else { + SetTitle(C_("title", "Open File")); + } + } + + void SetTitle(std::string title) override { + titleW = PrepareTitle(title); + ofn.lpstrTitle = titleW.c_str(); + } + + void SetCurrentName(std::string name) override { + SetFilename(GetFilename().Parent().Join(name)); + } + + Platform::Path GetFilename() override { + return Path::From(Narrow(filenameWC)); + } + + void SetFilename(Platform::Path path) override { + wcsncpy(filenameWC, Widen(path.raw).c_str(), sizeof(filenameWC) / sizeof(wchar_t) - 1); + } + + void AddFilter(std::string name, std::vector extensions) override { + std::string desc, patterns; + for(auto extension : extensions) { + std::string pattern = "*." + extension; + if(!desc.empty()) desc += ", "; + desc += pattern; + if(!patterns.empty()) patterns += ";"; + patterns += pattern; + } + filtersW += Widen(name + " (" + desc + ")" + '\0' + patterns + '\0'); + ofn.lpstrFilter = filtersW.c_str(); + if(ofn.lpstrDefExt == NULL) { + defExtW = Widen(extensions.front()); + ofn.lpstrDefExt = defExtW.c_str(); + } + } + + void FreezeChoices(SettingsRef settings, const std::string &key) override { + settings->FreezeString("Dialog_" + key + "_Folder", GetFilename().Parent().raw); + settings->FreezeInt("Dialog_" + key + "_Filter", ofn.nFilterIndex); + } + + void ThawChoices(SettingsRef settings, const std::string &key) override { + initialDirW = Widen(settings->ThawString("Dialog_" + key + "_Folder", "")); + ofn.lpstrInitialDir = initialDirW.c_str(); + ofn.nFilterIndex = settings->ThawInt("Dialog_" + key + "_Filter", 0); + } + + bool RunModal() override { + if(GetFilename().IsEmpty()) { + SetFilename(Path::From(_("untitled"))); + } + + if(isSaveDialog) { + return GetSaveFileNameW(&ofn) == TRUE; + } else { + return GetOpenFileNameW(&ofn) == TRUE; + } + } +}; + +FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) { + std::shared_ptr dialog = std::make_shared(); + dialog->ofn.hwndOwner = std::static_pointer_cast(parentWindow)->hWindow; + dialog->isSaveDialog = false; + return dialog; +} + +FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) { + std::shared_ptr dialog = std::make_shared(); + dialog->ofn.hwndOwner = std::static_pointer_cast(parentWindow)->hWindow; + dialog->isSaveDialog = true; + return dialog; +} + +//----------------------------------------------------------------------------- +// Application-wide APIs +//----------------------------------------------------------------------------- + +std::vector GetFontFiles() { + std::vector fonts; + + std::wstring fontsDirW(MAX_PATH, '\0'); + fontsDirW.resize(GetWindowsDirectoryW(&fontsDirW[0], fontsDirW.length())); + fontsDirW += L"\\fonts\\"; + Platform::Path fontsDir = Platform::Path::From(Narrow(fontsDirW)); + + WIN32_FIND_DATAW wfd; + HANDLE h = FindFirstFileW((fontsDirW + L"*").c_str(), &wfd); + while(h != INVALID_HANDLE_VALUE) { + fonts.push_back(fontsDir.Join(Narrow(wfd.cFileName))); + if(!FindNextFileW(h, &wfd)) break; + } + + return fonts; +} + +void OpenInBrowser(const std::string &url) { + ShellExecuteW(NULL, L"open", Widen(url).c_str(), NULL, NULL, SW_SHOWNORMAL); +} + +std::vector InitGui(int argc, char **argv) { + std::vector args = InitCli(argc, argv); + + INITCOMMONCONTROLSEX icc; + icc.dwSize = sizeof(icc); + icc.dwICC = ICC_STANDARD_CLASSES|ICC_BAR_CLASSES; + InitCommonControlsEx(&icc); + + if(!SetLocale((uint16_t)GetUserDefaultLCID())) { + SetLocale("en_US"); + } + + return args; +} + +void RunGui() { + MSG msg; + // The return value of the following functions doesn't indicate success or failure. + while(GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +void ExitGui() { + PostQuitMessage(0); +} + +void ClearGui() {} + +} +} diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp new file mode 100644 index 0000000..f025c86 --- /dev/null +++ b/src/platform/platform.cpp @@ -0,0 +1,713 @@ +//----------------------------------------------------------------------------- +// Platform-dependent functionality. +// +// Copyright 2017 whitequark +//----------------------------------------------------------------------------- +#if defined(__APPLE__) +// Include Apple headers before solvespace.h to avoid identifier clashes. +# include +# include +# include +#endif +#include "solvespace.h" +#include "mimalloc.h" +#include "config.h" +#if defined(WIN32) +// Conversely, include Microsoft headers after solvespace.h to avoid clashes. +# include +# include +#else +# include +# include +#endif + +namespace SolveSpace { +namespace Platform { + +//----------------------------------------------------------------------------- +// UTF-8 ⟷ UTF-16 conversion, on Windows. +//----------------------------------------------------------------------------- + +#if defined(WIN32) + +std::string Narrow(const wchar_t *in) +{ + std::string out; + DWORD len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL); + out.resize(len - 1); + ssassert(WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], len, NULL, NULL), + "Invalid UTF-16"); + return out; +} + +std::string Narrow(const std::wstring &in) +{ + if(in == L"") return ""; + + std::string out; + out.resize(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(), + NULL, 0, NULL, NULL)); + ssassert(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(), + &out[0], (int)out.length(), NULL, NULL), + "Invalid UTF-16"); + return out; +} + +std::wstring Widen(const char *in) +{ + std::wstring out; + DWORD len = MultiByteToWideChar(CP_UTF8, 0, in, -1, NULL, 0); + out.resize(len - 1); + ssassert(MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], len), + "Invalid UTF-8"); + return out; +} + +std::wstring Widen(const std::string &in) +{ + if(in == "") return L""; + + std::wstring out; + out.resize(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(), NULL, 0)); + ssassert(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(), + &out[0], (int)out.length()), + "Invalid UTF-8"); + return out; +} + +#endif + +//----------------------------------------------------------------------------- +// Path utility functions. +//----------------------------------------------------------------------------- + +static std::vector Split(const std::string &joined, char separator) { + std::vector parts; + + size_t oldpos = 0, pos = 0; + while(true) { + oldpos = pos; + pos = joined.find(separator, pos); + if(pos == std::string::npos) break; + parts.push_back(joined.substr(oldpos, pos - oldpos)); + pos += 1; + } + + if(oldpos != joined.length() - 1) { + parts.push_back(joined.substr(oldpos)); + } + + return parts; +} + +static std::string Concat(const std::vector &parts, char separator) { + std::string joined; + + bool first = true; + for(auto &part : parts) { + if(!first) joined += separator; + joined += part; + first = false; + } + + return joined; +} + +//----------------------------------------------------------------------------- +// Path manipulation. +//----------------------------------------------------------------------------- + +#if defined(WIN32) +const char SEPARATOR = '\\'; +#else +const char SEPARATOR = '/'; +#endif + +Path Path::From(std::string raw) { + Path path = { raw }; + return path; +} + +Path Path::CurrentDirectory() { +#if defined(WIN32) + // On Windows, OpenFile needs an absolute UNC path proper, so get that. + std::wstring rawW; + rawW.resize(GetCurrentDirectoryW(0, NULL)); + DWORD length = GetCurrentDirectoryW((int)rawW.length(), &rawW[0]); + ssassert(length > 0 && length == rawW.length() - 1, "Cannot get current directory"); + rawW.resize(length); + return From(Narrow(rawW)); +#else + char *raw = getcwd(NULL, 0); + ssassert(raw != NULL, "Cannot get current directory"); + Path path = From(raw); + free(raw); + return path; +#endif +} + +std::string Path::FileName() const { + std::string fileName = raw; + size_t slash = fileName.rfind(SEPARATOR); + if(slash != std::string::npos) { + fileName = fileName.substr(slash + 1); + } + return fileName; +} + +std::string Path::FileStem() const { + std::string baseName = FileName(); + size_t dot = baseName.rfind('.'); + if(dot != std::string::npos) { + baseName = baseName.substr(0, dot); + } + return baseName; +} + +std::string Path::Extension() const { + size_t dot = raw.rfind('.'); + if(dot != std::string::npos) { + return raw.substr(dot + 1); + } + return ""; +} + +bool Path::HasExtension(std::string theirExt) const { + std::string ourExt = Extension(); + std::transform(ourExt.begin(), ourExt.end(), ourExt.begin(), ::tolower); + std::transform(theirExt.begin(), theirExt.end(), theirExt.begin(), ::tolower); + return ourExt == theirExt; +} + +Path Path::WithExtension(std::string ext) const { + Path withExt = *this; + size_t dot = withExt.raw.rfind('.'); + if(dot != std::string::npos) { + withExt.raw.erase(dot); + } + withExt.raw += "."; + withExt.raw += ext; + return withExt; +} + +static void FindPrefix(const std::string &raw, size_t *pos) { + *pos = std::string::npos; +#if defined(WIN32) + if(raw.size() >= 7 && raw[2] == '?' && raw[3] == '\\' && + isalpha(raw[4]) && raw[5] == ':' && raw[6] == '\\') { + *pos = 7; + } else if(raw.size() >= 3 && isalpha(raw[0]) && raw[1] == ':' && raw[2] == '\\') { + *pos = 3; + } else if(raw.size() >= 2 && raw[0] == '\\' && raw[1] == '\\') { + size_t slashAt = raw.find('\\', 2); + if(slashAt != std::string::npos) { + *pos = raw.find('\\', slashAt + 1); + } + } +#else + if(!raw.empty() && raw[0] == '/') { + *pos = 1; + } +#endif +} + +bool Path::IsAbsolute() const { + size_t pos; + FindPrefix(raw, &pos); + return pos != std::string::npos; +} + +// Removes one component from the end of the path. +// Returns an empty path if the path consists only of a root. +Path Path::Parent() const { + Path parent = { raw }; + if(!parent.raw.empty() && parent.raw.back() == SEPARATOR) { + parent.raw.pop_back(); + } + size_t slash = parent.raw.rfind(SEPARATOR); + if(slash != std::string::npos) { + parent.raw = parent.raw.substr(0, slash + 1); + } else { + parent.raw.clear(); + } + if(IsAbsolute() && !parent.IsAbsolute()) { + return From(""); + } + return parent; +} + +// Concatenates a component to this path. +// Returns an empty path if this path or the component is empty. +Path Path::Join(const std::string &component) const { + ssassert(component.find(SEPARATOR) == std::string::npos, + "Use the Path::Join(const Path &) overload to append an entire path"); + return Join(Path::From(component)); +} + +// Concatenates a relative path to this path. +// Returns an empty path if either path is empty, or the other path is absolute. +Path Path::Join(const Path &other) const { + if(IsEmpty() || other.IsEmpty() || other.IsAbsolute()) { + return From(""); + } + + Path joined = { raw }; + if(joined.raw.back() != SEPARATOR) { + joined.raw += SEPARATOR; + } + joined.raw += other.raw; + return joined; +} + +// Expands the "." and ".." components in this path. +// On Windows, additionally prepends the UNC prefix to absolute paths without one. +// Returns an empty path if a ".." component would escape from the root. +Path Path::Expand(bool fromCurrentDirectory) const { + Path source; + Path expanded; + + if(fromCurrentDirectory && !IsAbsolute()) { + source = CurrentDirectory().Join(*this); + } else { + source = *this; + } + + size_t splitAt; + FindPrefix(source.raw, &splitAt); + if(splitAt != std::string::npos) { + expanded.raw = source.raw.substr(0, splitAt); + } else { + splitAt = 0; + } + + std::vector expandedComponents; + for(std::string component : Split(source.raw.substr(splitAt), SEPARATOR)) { + if(component == ".") { + // skip + } else if(component == "..") { + if(!expandedComponents.empty()) { + expandedComponents.pop_back(); + } else { + return From(""); + } + } else if(!component.empty()) { + expandedComponents.push_back(component); + } + } + + if(expanded.IsEmpty()) { + if(expandedComponents.empty()) { + expandedComponents.emplace_back("."); + } + expanded = From(Concat(expandedComponents, SEPARATOR)); + } else if(!expandedComponents.empty()) { + expanded = expanded.Join(From(Concat(expandedComponents, SEPARATOR))); + } + +#if defined(WIN32) + if(expanded.IsAbsolute() && expanded.raw.substr(0, 2) != "\\\\") { + expanded.raw = "\\\\?\\" + expanded.raw; + } +#endif + + return expanded; +} + +static std::string FilesystemNormalize(const std::string &str) { +#if defined(WIN32) + std::wstring strW = Widen(str); + std::transform(strW.begin(), strW.end(), strW.begin(), towlower); + return Narrow(strW); +#elif defined(__APPLE__) + CFMutableStringRef cfStr = + CFStringCreateMutableCopy(NULL, 0, + CFStringCreateWithBytesNoCopy(NULL, (const UInt8*)str.data(), str.size(), + kCFStringEncodingUTF8, /*isExternalRepresentation=*/false, kCFAllocatorNull)); + CFStringLowercase(cfStr, NULL); + std::string normalizedStr; + normalizedStr.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfStr)); + CFStringGetFileSystemRepresentation(cfStr, &normalizedStr[0], normalizedStr.size()); + normalizedStr.erase(normalizedStr.find('\0')); + return normalizedStr; +#else + return str; +#endif +} + +bool Path::Equals(const Path &other) const { + return FilesystemNormalize(raw) == FilesystemNormalize(other.raw); +} + +// Returns a relative path from a given base path. +// Returns an empty path if any of the paths is not absolute, or +// if they belong to different roots, or +// if they cannot be expanded. +Path Path::RelativeTo(const Path &base) const { + Path expanded = Expand(); + Path baseExpanded = base.Expand(); + if(!(expanded.IsAbsolute() && baseExpanded.IsAbsolute())){ + return From(""); + } + + size_t splitAt; + FindPrefix(expanded.raw, &splitAt); + size_t baseSplitAt; + FindPrefix(baseExpanded.raw, &baseSplitAt); + if(FilesystemNormalize(expanded.raw.substr(0, splitAt)) != + FilesystemNormalize(baseExpanded.raw.substr(0, splitAt))) { + return From(""); + } + + std::vector components = + Split(expanded.raw.substr(splitAt), SEPARATOR); + std::vector baseComponents = + Split(baseExpanded.raw.substr(baseSplitAt), SEPARATOR); + size_t common; + for(common = 0; common < baseComponents.size() && + common < components.size(); common++) { + if(FilesystemNormalize(baseComponents[common]) != + FilesystemNormalize(components[common])) { + break; + } + } + + std::vector resultComponents; + for(size_t i = common; i < baseComponents.size(); i++) { + resultComponents.emplace_back(".."); + } + resultComponents.insert(resultComponents.end(), + components.begin() + common, components.end()); + if(resultComponents.empty()) { + resultComponents.emplace_back("."); + } + return From(Concat(resultComponents, SEPARATOR)); +} + +Path Path::FromPortable(const std::string &repr) { + return From(Concat(Split(repr, '/'), SEPARATOR)); +} + +std::string Path::ToPortable() const { + ssassert(!IsAbsolute(), "absolute paths cannot be made portable"); + + return Concat(Split(raw, SEPARATOR), '/'); +} + +//----------------------------------------------------------------------------- +// File manipulation. +//----------------------------------------------------------------------------- + +FILE *OpenFile(const Platform::Path &filename, const char *mode) { + ssassert(filename.raw.length() == strlen(filename.raw.c_str()), + "Unexpected null byte in middle of a path"); +#if defined(WIN32) + return _wfopen(Widen(filename.Expand(/*fromCurrentDirectory=*/true).raw).c_str(), Widen(mode).c_str()); +#else + return fopen(filename.raw.c_str(), mode); +#endif +} + +bool FileExists(const Platform::Path &filename) { + FILE *f = OpenFile(filename, "rb"); + if(f == NULL) return false; + fclose(f); + return true; +} + +void RemoveFile(const Platform::Path &filename) { + ssassert(filename.raw.length() == strlen(filename.raw.c_str()), + "Unexpected null byte in middle of a path"); +#if defined(WIN32) + _wremove(Widen(filename.Expand().raw).c_str()); +#else + remove(filename.raw.c_str()); +#endif +} + +bool ReadFile(const Platform::Path &filename, std::string *data) { + FILE *f = OpenFile(filename, "rb"); + if(f == NULL) return false; + + if(fseek(f, 0, SEEK_END) != 0) + return false; + data->resize(ftell(f)); + if(fseek(f, 0, SEEK_SET) != 0) + return false; + if(fread(&(*data)[0], 1, data->size(), f) != data->size()) + return false; + if(fclose(f) != 0) + return false; + + return true; +} + +bool WriteFile(const Platform::Path &filename, const std::string &data) { + FILE *f = OpenFile(filename, "wb"); + if(f == NULL) return false; + + if(fwrite(&data[0], 1, data.size(), f) != data.size()) + return false; + if(fclose(f) != 0) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Loading resources, on Windows. +//----------------------------------------------------------------------------- + +#if defined(WIN32) + +const void *LoadResource(const std::string &name, size_t *size) { + HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), RT_RCDATA); + ssassert(hres != NULL, "Cannot find resource"); + HGLOBAL res = ::LoadResource(NULL, hres); + ssassert(res != NULL, "Cannot load resource"); + + *size = SizeofResource(NULL, hres); + return LockResource(res); +} + +#endif + +//----------------------------------------------------------------------------- +// Loading resources, on *nix. +//----------------------------------------------------------------------------- + +#if defined(__APPLE__) + +static Platform::Path PathFromCFURL(CFURLRef cfUrl) { + Path path; + CFStringRef cfPath = CFURLCopyFileSystemPath(cfUrl, kCFURLPOSIXPathStyle); + path.raw.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfPath)); + CFStringGetFileSystemRepresentation(cfPath, &path.raw[0], path.raw.size()); + path.raw.erase(path.raw.find('\0')); + CFRelease(cfPath); + return path; +} + +static Platform::Path ResourcePath(const std::string &name) { + Path path; + + // First, try to get the URL from the bundle. + CFStringRef cfName = CFStringCreateWithCString(kCFAllocatorDefault, name.c_str(), + kCFStringEncodingUTF8); + CFURLRef cfUrl = CFBundleCopyResourceURL(CFBundleGetMainBundle(), cfName, NULL, NULL); + if(cfUrl != NULL) { + path = PathFromCFURL(cfUrl); + CFRelease(cfUrl); + } + CFRelease(cfName); + + if(!path.IsEmpty()) return path; + + // If that failed, it means we aren't running from the bundle. + // Reference off the executable path, then. + cfUrl = CFBundleCopyExecutableURL(CFBundleGetMainBundle()); + if(cfUrl != NULL) { + path = PathFromCFURL(cfUrl).Parent().Parent().Join("res"); + path = path.Join(Path::FromPortable(name)); + CFRelease(cfUrl); + } + + return path; +} + +#elif !defined(WIN32) + +# if defined(__linux__) +static const char *selfSymlink = "/proc/self/exe"; +# elif defined(__NetBSD__) +static const char *selfSymlink = "/proc/curproc/exe"; +# elif defined(__OpenBSD__) || defined(__FreeBSD__) +static const char *selfSymlink = "/proc/curproc/file"; +# else +static const char *selfSymlink = ""; +# endif + +static Platform::Path FindLocalResourceDir() { + // Find out the path to the running binary. + Platform::Path selfPath; + char *expandedSelfPath = realpath(selfSymlink, NULL); + if(expandedSelfPath != NULL) { + selfPath = Path::From(expandedSelfPath); + } + free(expandedSelfPath); + + Platform::Path resourceDir; + if(selfPath.IsEmpty()) { + // We don't know how to find the local resource directory on this platform, + // so use the global one (by returning an empty string). + return Path::From(UNIX_DATADIR); + } else { + resourceDir = selfPath.Parent().Parent().Join("res"); + } + + struct stat st; + if(stat(resourceDir.raw.c_str(), &st) != -1) { + // An executable-adjacent resource directory exists, good. + return resourceDir; + } + + resourceDir = selfPath.Parent().Parent().Join("share").Join("solvespace"); + if(stat(resourceDir.raw.c_str(), &st) != -1) { + // A resource directory exists at a relative path, good. + return resourceDir; + } + + // No executable-adjacent resource directory; use the one from compile-time prefix. + return Path::From(UNIX_DATADIR); +} + +static Platform::Path ResourcePath(const std::string &name) { + static Platform::Path resourceDir; + if(resourceDir.IsEmpty()) { + resourceDir = FindLocalResourceDir(); + } + + return resourceDir.Join(Path::FromPortable(name)); +} + +#endif + +#if !defined(WIN32) + +const void *LoadResource(const std::string &name, size_t *size) { + static std::map cache; + + auto it = cache.find(name); + if(it == cache.end()) { + ssassert(ReadFile(ResourcePath(name), &cache[name]), "Cannot read resource"); + it = cache.find(name); + } + + const std::string &content = (*it).second; + *size = content.size(); + return (const void*)content.data(); +} + +#endif + +//----------------------------------------------------------------------------- +// Startup and command-line argument handling, on Windows. +//----------------------------------------------------------------------------- + +#if defined(WIN32) + +std::vector InitCli(int argc, char **argv) { +#if defined(_MSC_VER) + // We display our own message on abort; just call ReportFault. + _set_abort_behavior(_CALL_REPORTFAULT, _WRITE_ABORT_MSG|_CALL_REPORTFAULT); + int crtReportTypes[] = {_CRT_WARN, _CRT_ERROR, _CRT_ASSERT}; + for(int crtReportType : crtReportTypes) { + _CrtSetReportMode(crtReportType, _CRTDBG_MODE_FILE|_CRTDBG_MODE_DEBUG); + _CrtSetReportFile(crtReportType, _CRTDBG_FILE_STDERR); + } +#endif + + // Extract the command-line arguments; the ones from main() are ignored, + // since they are in the OEM encoding. + int argcW; + LPWSTR *argvW = CommandLineToArgvW(GetCommandLineW(), &argcW); + std::vector args; + for(int i = 0; i < argcW; i++) + args.push_back(Platform::Narrow(argvW[i])); + LocalFree(argvW); + return args; +} + +#endif + +//----------------------------------------------------------------------------- +// Startup and command-line argument handling, on *nix. +//----------------------------------------------------------------------------- + +#if !defined(WIN32) + +std::vector InitCli(int argc, char **argv) { + return {&argv[0], &argv[argc]}; +} + +#endif + +//----------------------------------------------------------------------------- +// Debug output, on Windows. +//----------------------------------------------------------------------------- + +#if defined(WIN32) + +void DebugPrint(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + int len = _vscprintf(fmt, va) + 1; + va_end(va); + + va_start(va, fmt); + char *buf = (char *)_alloca(len); + _vsnprintf(buf, len, fmt, va); + va_end(va); + + // The native version of OutputDebugString, unlike most others, + // is OutputDebugStringA. + OutputDebugStringA(buf); + OutputDebugStringA("\n"); + +#ifndef NDEBUG + // Duplicate to stderr in debug builds, but not in release; this is slow. + fputs(buf, stderr); + fputc('\n', stderr); +#endif +} + +#endif + +//----------------------------------------------------------------------------- +// Debug output, on *nix. +//----------------------------------------------------------------------------- + +#if !defined(WIN32) + +void DebugPrint(const char *fmt, ...) { + va_list va; + va_start(va, fmt); + vfprintf(stderr, fmt, va); + fputc('\n', stderr); + va_end(va); +} + +#endif + +//----------------------------------------------------------------------------- +// Temporary arena. +//----------------------------------------------------------------------------- + +struct MimallocHeap { + mi_heap_t *heap = NULL; + + ~MimallocHeap() { + if(heap != NULL) + mi_heap_destroy(heap); + } +}; + +static thread_local MimallocHeap TempArena; + +void *AllocTemporary(size_t size) { + if(TempArena.heap == NULL) { + TempArena.heap = mi_heap_new(); + ssassert(TempArena.heap != NULL, "out of memory"); + } + void *ptr = mi_heap_zalloc(TempArena.heap, size); + ssassert(ptr != NULL, "out of memory"); + return ptr; +} + +void FreeAllTemporary() { + MimallocHeap temp; + std::swap(TempArena.heap, temp.heap); +} + +} +} diff --git a/src/platform/platform.h b/src/platform/platform.h new file mode 100644 index 0000000..5664fa2 --- /dev/null +++ b/src/platform/platform.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// Platform-dependent functionality. +// +// Copyright 2017 whitequark +//----------------------------------------------------------------------------- + +#ifndef SOLVESPACE_PLATFORM_H +#define SOLVESPACE_PLATFORM_H + +namespace Platform { + +// UTF-8 ⟷ UTF-16 conversion, for Windows. +#if defined(WIN32) +std::string Narrow(const wchar_t *s); +std::wstring Widen(const char *s); +std::string Narrow(const std::wstring &s); +std::wstring Widen(const std::string &s); +#endif + +// A filesystem path, respecting the conventions of the current platform. +// Transformation functions return an empty path on error. +class Path { +public: + std::string raw; + + static Path From(std::string raw); + static Path CurrentDirectory(); + + void Clear() { raw.clear(); } + + bool Equals(const Path &other) const; + bool IsEmpty() const { return raw.empty(); } + bool IsAbsolute() const; + bool HasExtension(std::string ext) const; + + std::string FileName() const; + std::string FileStem() const; + std::string Extension() const; + + Path WithExtension(std::string ext) const; + Path Parent() const; + Path Join(const std::string &component) const; + Path Join(const Path &other) const; + Path Expand(bool fromCurrentDirectory = false) const; + Path RelativeTo(const Path &base) const; + + // Converting to and from a platform-independent representation + // (conventionally, the Unix one). + static Path FromPortable(const std::string &repr); + std::string ToPortable() const; +}; + +struct PathLess { + bool operator()(const Path &a, const Path &b) const { return a.raw < b.raw; } +}; + +// File manipulation functions. +bool FileExists(const Platform::Path &filename); +FILE *OpenFile(const Platform::Path &filename, const char *mode); +bool ReadFile(const Platform::Path &filename, std::string *data); +bool WriteFile(const Platform::Path &filename, const std::string &data); +void RemoveFile(const Platform::Path &filename); + +// Resource loading function. +const void *LoadResource(const std::string &name, size_t *size); + +// Startup and command-line argument handling. +std::vector InitCli(int argc, char **argv); + +// Debug print function. +void DebugPrint(const char *fmt, ...); + +// Temporary arena functions. +void *AllocTemporary(size_t size); +void FreeAllTemporary(); + +} + +#endif diff --git a/src/polygon.cpp b/src/polygon.cpp index eae40fe..f325fd9 100644 --- a/src/polygon.cpp +++ b/src/polygon.cpp @@ -5,12 +5,12 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -Vector STriangle::Normal(void) { +Vector STriangle::Normal() const { Vector ab = b.Minus(a), bc = c.Minus(b); return ab.Cross(bc); } -double STriangle::MinAltitude(void) { +double STriangle::MinAltitude() const { double altA = a.DistanceToLine(b, c.Minus(b)), altB = b.DistanceToLine(c, a.Minus(c)), altC = c.DistanceToLine(a, b.Minus(a)); @@ -18,7 +18,7 @@ double STriangle::MinAltitude(void) { return min(altA, min(altB, altC)); } -bool STriangle::ContainsPoint(Vector p) { +bool STriangle::ContainsPoint(Vector p) const { Vector n = Normal(); if(MinAltitude() < LENGTH_EPS) { // shouldn't happen; zero-area triangle @@ -27,7 +27,7 @@ bool STriangle::ContainsPoint(Vector p) { return ContainsPointProjd(n.WithMagnitude(1), p); } -bool STriangle::ContainsPointProjd(Vector n, Vector p) { +bool STriangle::ContainsPointProjd(Vector n, Vector p) const { Vector ab = b.Minus(a), bc = c.Minus(b), ca = a.Minus(c); Vector no_ab = n.Cross(ab); @@ -42,11 +42,79 @@ bool STriangle::ContainsPointProjd(Vector n, Vector p) { return true; } -void STriangle::FlipNormal(void) { +bool STriangle::Raytrace(const Vector &rayPoint, const Vector &rayDir, + double *t, Vector *inters) const { + // Algorithm from: "Fast, Minimum Storage Ray/Triangle Intersection" by + // Tomas Moeller and Ben Trumbore. + + // Find vectors for two edges sharing vertex A. + Vector edge1 = b.Minus(a); + Vector edge2 = c.Minus(a); + + // Begin calculating determinant - also used to calculate U parameter. + Vector pvec = rayDir.Cross(edge2); + + // If determinant is near zero, ray lies in plane of triangle. + // Also, cull back facing triangles here. + double det = edge1.Dot(pvec); + if(-det < LENGTH_EPS) return false; + double inv_det = 1.0f / det; + + // Calculate distance from vertex A to ray origin. + Vector tvec = rayPoint.Minus(a); + + // Calculate U parameter and test bounds. + double u = tvec.Dot(pvec) * inv_det; + if (u < 0.0f || u > 1.0f) return false; + + // Prepare to test V parameter. + Vector qvec = tvec.Cross(edge1); + + // Calculate V parameter and test bounds. + double v = rayDir.Dot(qvec) * inv_det; + if (v < 0.0f || u + v > 1.0f) return false; + + // Calculate t, ray intersects triangle. + *t = edge2.Dot(qvec) * inv_det; + + // Calculate intersection point. + if(inters != NULL) *inters = rayPoint.Plus(rayDir.ScaledBy(*t)); + + return true; +} + +double STriangle::SignedVolume() const { + return a.Dot(b.Cross(c)) / 6.0; +} + +double STriangle::Area() const { + Vector ab = a.Minus(b); + Vector cb = c.Minus(b); + return ab.Cross(cb).Magnitude() / 2.0; +} + +bool STriangle::IsDegenerate() const { + return a.OnLineSegment(b, c) || + b.OnLineSegment(a, c) || + c.OnLineSegment(a, b); +} + +void STriangle::FlipNormal() { swap(a, b); swap(an, bn); } +STriangle STriangle::Transform(Vector u, Vector v, Vector n) const { + STriangle tr = *this; + tr.a = tr.a.ScaleOutOfCsys(u, v, n); + tr.an = tr.an.ScaleOutOfCsys(u, v, n); + tr.b = tr.b.ScaleOutOfCsys(u, v, n); + tr.bn = tr.bn.ScaleOutOfCsys(u, v, n); + tr.c = tr.c.ScaleOutOfCsys(u, v, n); + tr.cn = tr.cn.ScaleOutOfCsys(u, v, n); + return tr; +} + STriangle STriangle::From(STriMeta meta, Vector a, Vector b, Vector c) { STriangle tr = {}; tr.meta = meta; @@ -63,7 +131,7 @@ SEdge SEdge::From(Vector a, Vector b) { return se; } -bool SEdge::EdgeCrosses(Vector ea, Vector eb, Vector *ppi, SPointList *spl) { +bool SEdge::EdgeCrosses(Vector ea, Vector eb, Vector *ppi, SPointList *spl) const { Vector d = eb.Minus(ea); double t_eps = LENGTH_EPS/d.Magnitude(); @@ -89,20 +157,20 @@ bool SEdge::EdgeCrosses(Vector ea, Vector eb, Vector *ppi, SPointList *spl) { if(sqrt(fabs(d.Dot(dthis))) > (m - LENGTH_EPS)) { // The edges are parallel. if(fabs(a.DistanceToLine(ea, d)) > LENGTH_EPS) { - // and not coincident, so can't be interesecting + // and not coincident, so can't be intersecting return false; } // The edges are coincident. Make sure that neither endpoint lies // on the other bool inters = false; double t; - t = a.Minus(ea).DivPivoting(d); + t = a.Minus(ea).DivProjected(d); if(t > t_eps && t < (1 - t_eps)) inters = true; - t = b.Minus(ea).DivPivoting(d); + t = b.Minus(ea).DivProjected(d); if(t > t_eps && t < (1 - t_eps)) inters = true; - t = ea.Minus(a).DivPivoting(dthis); + t = ea.Minus(a).DivProjected(dthis); if(t > tthis_eps && t < (1 - tthis_eps)) inters = true; - t = eb.Minus(a).DivPivoting(dthis); + t = eb.Minus(a).DivProjected(dthis); if(t > tthis_eps && t < (1 - tthis_eps)) inters = true; if(inters) { @@ -141,12 +209,13 @@ bool SEdge::EdgeCrosses(Vector ea, Vector eb, Vector *ppi, SPointList *spl) { return false; } -void SEdgeList::Clear(void) { +void SEdgeList::Clear() { l.Clear(); } -void SEdgeList::AddEdge(Vector a, Vector b, int auxA, int auxB) { +void SEdgeList::AddEdge(Vector a, Vector b, int auxA, int auxB, int tag) { SEdge e = {}; + e.tag = tag; e.a = a; e.b = b; e.auxA = auxA; @@ -155,7 +224,7 @@ void SEdgeList::AddEdge(Vector a, Vector b, int auxA, int auxB) { } bool SEdgeList::AssembleContour(Vector first, Vector last, SContour *dest, - SEdge *errorAt, bool keepDir) + SEdge *errorAt, bool keepDir, int start) const { int i; @@ -163,8 +232,9 @@ bool SEdgeList::AssembleContour(Vector first, Vector last, SContour *dest, dest->AddPoint(last); do { - for(i = 0; i < l.n; i++) { - SEdge *se = &(l.elem[i]); + for(i = start; i < l.n; i++) { + /// @todo fix const! + SEdge *se = const_cast(&(l[i])); if(se->tag) continue; if(se->a.Equals(last)) { @@ -195,36 +265,29 @@ bool SEdgeList::AssembleContour(Vector first, Vector last, SContour *dest, return true; } -bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir) { +bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir) const { dest->Clear(); bool allClosed = true; - for(;;) { - Vector first = Vector::From(0, 0, 0); - Vector last = Vector::From(0, 0, 0); - int i; - for(i = 0; i < l.n; i++) { - if(!l.elem[i].tag) { - first = l.elem[i].a; - last = l.elem[i].b; - l.elem[i].tag = 1; - break; + Vector first = Vector::From(0, 0, 0); + Vector last = Vector::From(0, 0, 0); + int i; + for(i = 0; i < l.n; i++) { + if(!l[i].tag) { + first = l[i].a; + last = l[i].b; + /// @todo fix const! + const_cast(&(l[i]))->tag = 1; + // Create a new empty contour in our polygon, and finish assembling + // into that contour. + dest->AddEmptyContour(); + if(!AssembleContour(first, last, dest->l.Last(), errorAt, keepDir, i+1)) { + allClosed = false; } + // But continue assembling, even if some of the contours are open } - if(i >= l.n) { - return allClosed; - } - - // Create a new empty contour in our polygon, and finish assembling - // into that contour. - dest->AddEmptyContour(); - if(!AssembleContour(first, last, &(dest->l.elem[dest->l.n-1]), - errorAt, keepDir)) - { - allClosed = false; - } - // But continue assembling, even if some of the contours are open } + return allClosed; } //----------------------------------------------------------------------------- @@ -233,33 +296,24 @@ bool SEdgeList::AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir) { // but they are considered to cross if they are coincident and overlapping. // If pi is not NULL, then a crossing is returned in that. //----------------------------------------------------------------------------- -int SEdgeList::AnyEdgeCrossings(Vector a, Vector b, - Vector *ppi, SPointList *spl) -{ - int cnt = 0; - SEdge *se; - for(se = l.First(); se; se = l.NextAfter(se)) { - if(se->EdgeCrosses(a, b, ppi, spl)) { - cnt++; - } - } - return cnt; +int SEdgeList::AnyEdgeCrossings(Vector a, Vector b, Vector *ppi, SPointList *spl) const { + auto cnt = std::count_if(l.begin(), l.end(), + [&](SEdge const &se) { return se.EdgeCrosses(a, b, ppi, spl); }); + return static_cast(cnt); } //----------------------------------------------------------------------------- // Returns true if the intersecting edge list contains an edge that shares // an endpoint with one of our edges. //----------------------------------------------------------------------------- -bool SEdgeList::ContainsEdgeFrom(SEdgeList *sel) { - SEdge *se; - for(se = l.First(); se; se = l.NextAfter(se)) { +bool SEdgeList::ContainsEdgeFrom(const SEdgeList *sel) const { + for(const SEdge *se = l.First(); se; se = l.NextAfter(se)) { if(sel->ContainsEdge(se)) return true; } return false; } -bool SEdgeList::ContainsEdge(SEdge *set) { - SEdge *se; - for(se = l.First(); se; se = l.NextAfter(se)) { +bool SEdgeList::ContainsEdge(const SEdge *set) const { + for(const SEdge *se = l.First(); se; se = l.NextAfter(se)) { if((se->a).Equals(set->a) && (se->b).Equals(set->b)) return true; if((se->b).Equals(set->a) && (se->a).Equals(set->b)) return true; } @@ -267,23 +321,26 @@ bool SEdgeList::ContainsEdge(SEdge *set) { } //----------------------------------------------------------------------------- -// Remove unnecessary edges: if two are anti-parallel then remove both, and if -// two are parallel then remove one. +// Remove unnecessary edges: +// - if two are anti-parallel then +// if both=true, remove both +// else remove only one. +// - if two are parallel then remove one. //----------------------------------------------------------------------------- -void SEdgeList::CullExtraneousEdges(void) { +void SEdgeList::CullExtraneousEdges(bool both) { l.ClearTags(); - int i, j; - for(i = 0; i < l.n; i++) { - SEdge *se = &(l.elem[i]); - for(j = i+1; j < l.n; j++) { - SEdge *set = &(l.elem[j]); + for(int i = 0; i < l.n; i++) { + SEdge *se = &(l[i]); + for(int j = i + 1; j < l.n; j++) { + SEdge *set = &(l[j]); if((set->a).Equals(se->a) && (set->b).Equals(se->b)) { // Two parallel edges exist; so keep only the first one. set->tag = 1; } if((set->a).Equals(se->b) && (set->b).Equals(se->a)) { - // Two anti-parallel edges exist; so keep neither. - se->tag = 1; + // Two anti-parallel edges exist; if both=true, keep neither, + // otherwise keep only one. + if (both) se->tag = 1; set->tag = 1; } } @@ -295,12 +352,12 @@ void SEdgeList::CullExtraneousEdges(void) { // Make a kd-tree of edges. This is used for O(log(n)) implementations of stuff // that would naively be O(n). //----------------------------------------------------------------------------- -SKdNodeEdges *SKdNodeEdges::Alloc(void) { +SKdNodeEdges *SKdNodeEdges::Alloc() { SKdNodeEdges *ne = (SKdNodeEdges *)AllocTemporary(sizeof(SKdNodeEdges)); *ne = {}; return ne; } -SEdgeLl *SEdgeLl::Alloc(void) { +SEdgeLl *SEdgeLl::Alloc() { SEdgeLl *sell = (SEdgeLl *)AllocTemporary(sizeof(SEdgeLl)); *sell = {}; return sell; @@ -395,7 +452,7 @@ SKdNodeEdges *SKdNodeEdges::From(SEdgeLl *sell) { } int SKdNodeEdges::AnyEdgeCrossings(Vector a, Vector b, int cnt, - Vector *pi, SPointList *spl) + Vector *pi, SPointList *spl) const { int inters = 0; if(gt && lt) { @@ -427,49 +484,43 @@ int SKdNodeEdges::AnyEdgeCrossings(Vector a, Vector b, int cnt, // We have an edge list that contains only collinear edges, maybe with more // splits than necessary. Merge any collinear segments that join. //----------------------------------------------------------------------------- -static Vector LineStart, LineDirection; -static int ByTAlongLine(const void *av, const void *bv) -{ - SEdge *a = (SEdge *)av, - *b = (SEdge *)bv; - - double ta = (a->a.Minus(LineStart)).DivPivoting(LineDirection), - tb = (b->a.Minus(LineStart)).DivPivoting(LineDirection); - - return (ta > tb) ? 1 : -1; -} void SEdgeList::MergeCollinearSegments(Vector a, Vector b) { - LineStart = a; - LineDirection = b.Minus(a); - qsort(l.elem, l.n, sizeof(l.elem[0]), ByTAlongLine); + const Vector lineStart = a; + const Vector lineDirection = b.Minus(a); + std::sort(l.begin(), l.end(), [&](const SEdge &a, const SEdge &b) { + double ta = (a.a.Minus(lineStart)).DivProjected(lineDirection); + double tb = (b.a.Minus(lineStart)).DivProjected(lineDirection); + + return (ta < tb); + }); l.ClearTags(); - int i; - for(i = 1; i < l.n; i++) { - SEdge *prev = &(l.elem[i-1]), - *now = &(l.elem[i]); - - if((prev->b).Equals(now->a) && prev->auxA == now->auxA) { - // The previous segment joins up to us; so merge it into us. - prev->tag = 1; - now->a = prev->a; + SEdge *prev = nullptr; + for(auto &now : l) { + if(prev != nullptr) { + if((prev->b).Equals(now.a) && prev->auxA == now.auxA) { + // The previous segment joins up to us; so merge it into us. + prev->tag = 1; + now.a = prev->a; + } } + prev = &now; } l.RemoveTagged(); } -void SPointList::Clear(void) { +void SPointList::Clear() { l.Clear(); } -bool SPointList::ContainsPoint(Vector pt) { +bool SPointList::ContainsPoint(Vector pt) const { return (IndexForPoint(pt) >= 0); } -int SPointList::IndexForPoint(Vector pt) { +int SPointList::IndexForPoint(Vector pt) const { int i; for(i = 0; i < l.n; i++) { - SPoint *p = &(l.elem[i]); + const SPoint *p = &(l[i]); if(pt.Equals(p->p)) { return i; } @@ -506,36 +557,34 @@ void SContour::AddPoint(Vector p) { l.Add(&sp); } -void SContour::MakeEdgesInto(SEdgeList *el) { +void SContour::MakeEdgesInto(SEdgeList *el) const { int i; for(i = 0; i < (l.n - 1); i++) { - el->AddEdge(l.elem[i].p, l.elem[i+1].p); + el->AddEdge(l[i].p, l[i+1].p); } } -void SContour::CopyInto(SContour *dest) { - SPoint *sp; - for(sp = l.First(); sp; sp = l.NextAfter(sp)) { +void SContour::CopyInto(SContour *dest) const { + for(const SPoint *sp = l.First(); sp; sp = l.NextAfter(sp)) { dest->AddPoint(sp->p); } } -void SContour::FindPointWithMinX(void) { - SPoint *sp; +void SContour::FindPointWithMinX() { xminPt = Vector::From(1e10, 1e10, 1e10); - for(sp = l.First(); sp; sp = l.NextAfter(sp)) { + for(const SPoint *sp = l.First(); sp; sp = l.NextAfter(sp)) { if(sp->p.x < xminPt.x) { xminPt = sp->p; } } } -Vector SContour::ComputeNormal(void) { +Vector SContour::ComputeNormal() const { Vector n = Vector::From(0, 0, 0); for(int i = 0; i < l.n - 2; i++) { - Vector u = (l.elem[i+1].p).Minus(l.elem[i+0].p).WithMagnitude(1); - Vector v = (l.elem[i+2].p).Minus(l.elem[i+1].p).WithMagnitude(1); + Vector u = (l[i+1].p).Minus(l[i+0].p).WithMagnitude(1); + Vector v = (l[i+2].p).Minus(l[i+1].p).WithMagnitude(1); Vector nt = u.Cross(v); if(nt.Magnitude() > n.Magnitude()) { n = nt; @@ -544,12 +593,12 @@ Vector SContour::ComputeNormal(void) { return n.WithMagnitude(1); } -Vector SContour::AnyEdgeMidpoint(void) { - if(l.n < 2) oops(); - return ((l.elem[0].p).Plus(l.elem[1].p)).ScaledBy(0.5); +Vector SContour::AnyEdgeMidpoint() const { + ssassert(l.n >= 2, "Need two points to find a midpoint"); + return ((l[0].p).Plus(l[1].p)).ScaledBy(0.5); } -bool SContour::IsClockwiseProjdToNormal(Vector n) { +bool SContour::IsClockwiseProjdToNormal(Vector n) const { // Degenerate things might happen as we draw; doesn't really matter // what we do then. if(n.Magnitude() < 0.01) return true; @@ -557,24 +606,24 @@ bool SContour::IsClockwiseProjdToNormal(Vector n) { return (SignedAreaProjdToNormal(n) < 0); } -double SContour::SignedAreaProjdToNormal(Vector n) { +double SContour::SignedAreaProjdToNormal(Vector n) const { // An arbitrary 2d coordinate system that has n as its normal Vector u = n.Normal(0); Vector v = n.Normal(1); double area = 0; for(int i = 0; i < (l.n - 1); i++) { - double u0 = (l.elem[i ].p).Dot(u); - double v0 = (l.elem[i ].p).Dot(v); - double u1 = (l.elem[i+1].p).Dot(u); - double v1 = (l.elem[i+1].p).Dot(v); + double u0 = (l[i ].p).Dot(u); + double v0 = (l[i ].p).Dot(v); + double u1 = (l[i+1].p).Dot(u); + double v1 = (l[i+1].p).Dot(v); area += ((v0 + v1)/2)*(u1 - u0); } return area; } -bool SContour::ContainsPointProjdToNormal(Vector n, Vector p) { +bool SContour::ContainsPointProjdToNormal(Vector n, Vector p) const { Vector u = n.Normal(0); Vector v = n.Normal(1); @@ -583,11 +632,11 @@ bool SContour::ContainsPointProjdToNormal(Vector n, Vector p) { bool inside = false; for(int i = 0; i < (l.n - 1); i++) { - double ua = (l.elem[i ].p).Dot(u); - double va = (l.elem[i ].p).Dot(v); + double ua = (l[i ].p).Dot(u); + double va = (l[i ].p).Dot(v); // The curve needs to be exactly closed; approximation is death. - double ub = (l.elem[(i+1)%(l.n-1)].p).Dot(u); - double vb = (l.elem[(i+1)%(l.n-1)].p).Dot(v); + double ub = (l[(i+1)%(l.n-1)].p).Dot(u); + double vb = (l[(i+1)%(l.n-1)].p).Dot(v); if ((((va <= vp) && (vp < vb)) || ((vb <= vp) && (vp < va))) && @@ -600,82 +649,77 @@ bool SContour::ContainsPointProjdToNormal(Vector n, Vector p) { return inside; } -void SContour::Reverse(void) { +void SContour::Reverse() { l.Reverse(); } -void SPolygon::Clear(void) { +void SPolygon::Clear() { int i; for(i = 0; i < l.n; i++) { - (l.elem[i]).l.Clear(); + (l[i]).l.Clear(); } l.Clear(); } -void SPolygon::AddEmptyContour(void) { +void SPolygon::AddEmptyContour() { SContour c = {}; l.Add(&c); } -void SPolygon::MakeEdgesInto(SEdgeList *el) { +void SPolygon::MakeEdgesInto(SEdgeList *el) const { int i; for(i = 0; i < l.n; i++) { - (l.elem[i]).MakeEdgesInto(el); + (l[i]).MakeEdgesInto(el); } } -Vector SPolygon::ComputeNormal(void) { - if(l.n < 1) return Vector::From(0, 0, 0); - return (l.elem[0]).ComputeNormal(); +Vector SPolygon::ComputeNormal() const { + if(l.IsEmpty()) + return Vector::From(0, 0, 0); + return (l[0]).ComputeNormal(); } -double SPolygon::SignedArea(void) { - SContour *sc; +double SPolygon::SignedArea() const { double area = 0; // This returns the true area only if the contours are all oriented // correctly, with the holes backwards from the outer contours. - for(sc = l.First(); sc; sc = l.NextAfter(sc)) { + for(const SContour *sc = l.First(); sc; sc = l.NextAfter(sc)) { area += sc->SignedAreaProjdToNormal(normal); } return area; } -bool SPolygon::ContainsPoint(Vector p) { +bool SPolygon::ContainsPoint(Vector p) const { return (WindingNumberForPoint(p) % 2) == 1; } -int SPolygon::WindingNumberForPoint(Vector p) { - int winding = 0; - int i; - for(i = 0; i < l.n; i++) { - SContour *sc = &(l.elem[i]); - if(sc->ContainsPointProjdToNormal(normal, p)) { - winding++; - } - } +size_t SPolygon::WindingNumberForPoint(Vector p) const { + auto winding = std::count_if(l.begin(), l.end(), [&](const SContour &sc) { + return sc.ContainsPointProjdToNormal(normal, p); + }); return winding; } -void SPolygon::FixContourDirections(void) { +void SPolygon::FixContourDirections() { // At output, the contour's tag will be 1 if we reversed it, else 0. l.ClearTags(); // Outside curve looks counterclockwise, projected against our normal. int i, j; for(i = 0; i < l.n; i++) { - SContour *sc = &(l.elem[i]); + SContour *sc = &(l[i]); if(sc->l.n < 2) continue; // The contours may not intersect, but they may share vertices; so // testing a vertex for point-in-polygon may fail, but the midpoint // of an edge is okay. - Vector pt = (((sc->l.elem[0]).p).Plus(sc->l.elem[1].p)).ScaledBy(0.5); + Vector pt = (((sc->l[0]).p).Plus(sc->l[1].p)).ScaledBy(0.5); sc->timesEnclosed = 0; bool outer = true; for(j = 0; j < l.n; j++) { if(i == j) continue; - SContour *sct = &(l.elem[j]); + SContour *sct = &(l[j]); if(sct->ContainsPointProjdToNormal(normal, pt)) { outer = !outer; (sc->timesEnclosed)++; @@ -690,17 +734,18 @@ void SPolygon::FixContourDirections(void) { } } -bool SPolygon::IsEmpty(void) { - if(l.n == 0 || l.elem[0].l.n == 0) return true; +bool SPolygon::IsEmpty() const { + if(l.IsEmpty() || l[0].l.IsEmpty()) + return true; return false; } -Vector SPolygon::AnyPoint(void) { - if(IsEmpty()) oops(); - return l.elem[0].l.elem[0].p; +Vector SPolygon::AnyPoint() const { + ssassert(!IsEmpty(), "Need at least one point"); + return l[0].l[0].p; } -bool SPolygon::SelfIntersecting(Vector *intersectsAt) { +bool SPolygon::SelfIntersecting(Vector *intersectsAt) const { SEdgeList el = {}; MakeEdgesInto(&el); SKdNodeEdges *kdtree = SKdNodeEdges::From(&el); @@ -723,18 +768,29 @@ bool SPolygon::SelfIntersecting(Vector *intersectsAt) { return ret; } +void SPolygon::InverseTransformInto(SPolygon *sp, Vector u, Vector v, Vector n) const { + for(const SContour &sc : l) { + SContour tsc = {}; + tsc.timesEnclosed = sc.timesEnclosed; + for(const SPoint &sp : sc.l) { + tsc.AddPoint(sp.p.DotInToCsys(u, v, n)); + } + sp->l.Add(&tsc); + } +} + //----------------------------------------------------------------------------- // Low-quality routines to cutter radius compensate a polygon. Assumes the // polygon is in the xy plane, and the contours all go in the right direction // with respect to normal (0, 0, -1). //----------------------------------------------------------------------------- -void SPolygon::OffsetInto(SPolygon *dest, double r) { +void SPolygon::OffsetInto(SPolygon *dest, double r) const { int i; dest->Clear(); for(i = 0; i < l.n; i++) { - SContour *sc = &(l.elem[i]); + const SContour *sc = &(l[i]); dest->AddEmptyContour(); - sc->OffsetInto(&(dest->l.elem[dest->l.n-1]), r); + sc->OffsetInto(&(dest->l[dest->l.n-1]), r); } } //----------------------------------------------------------------------------- @@ -781,7 +837,7 @@ static bool IntersectionOfLines(double x0A, double y0A, double dxA, double dyA, return true; } -void SContour::OffsetInto(SContour *dest, double r) { +void SContour::OffsetInto(SContour *dest, double r) const { int i; for(i = 0; i < l.n; i++) { @@ -789,9 +845,9 @@ void SContour::OffsetInto(SContour *dest, double r) { Vector dp, dn; double thetan, thetap; - a = l.elem[WRAP(i-1, (l.n-1))].p; - b = l.elem[WRAP(i, (l.n-1))].p; - c = l.elem[WRAP(i+1, (l.n-1))].p; + a = l[WRAP(i-1, (l.n-1))].p; + b = l[WRAP(i, (l.n-1))].p; + c = l[WRAP(i+1, (l.n-1))].p; dp = a.Minus(b); thetap = atan2(dp.y, dp.x); diff --git a/src/polygon.h b/src/polygon.h index 7d2907b..cf0f322 100644 --- a/src/polygon.h +++ b/src/polygon.h @@ -5,8 +5,8 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#ifndef __POLYGON_H -#define __POLYGON_H +#ifndef SOLVESPACE_POLYGON_H +#define SOLVESPACE_POLYGON_H class SPointList; class SPolygon; @@ -15,6 +15,26 @@ class SMesh; class SBsp3; class SOutlineList; +enum class EarType : uint32_t { + UNKNOWN = 0, + NOT_EAR = 1, + EAR = 2 +}; + +enum class BspClass : uint32_t { + POS = 100, + NEG = 101, + COPLANAR = 200 +}; + +enum class EdgeKind : uint32_t { + NAKED_OR_SELF_INTER = 100, + SELF_INTER = 200, + TURNING = 300, + EMPHASIZED = 400, + SHARP = 500, +}; + class SEdge { public: int tag; @@ -22,23 +42,23 @@ public: Vector a, b; static SEdge From(Vector a, Vector b); - bool EdgeCrosses(Vector a, Vector b, Vector *pi=NULL, SPointList *spl=NULL); + bool EdgeCrosses(Vector a, Vector b, Vector *pi=NULL, SPointList *spl=NULL) const; }; class SEdgeList { public: List l; - void Clear(void); - void AddEdge(Vector a, Vector b, int auxA=0, int auxB=0); - bool AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir=false); + void Clear(); + void AddEdge(Vector a, Vector b, int auxA=0, int auxB=0, int tag=0); + bool AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir=false) const; bool AssembleContour(Vector first, Vector last, SContour *dest, - SEdge *errorAt, bool keepDir); + SEdge *errorAt, bool keepDir, int start) const; int AnyEdgeCrossings(Vector a, Vector b, - Vector *pi=NULL, SPointList *spl=NULL); - bool ContainsEdgeFrom(SEdgeList *sel); - bool ContainsEdge(SEdge *se); - void CullExtraneousEdges(void); + Vector *pi=NULL, SPointList *spl=NULL) const; + bool ContainsEdgeFrom(const SEdgeList *sel) const; + bool ContainsEdge(const SEdge *se) const; + void CullExtraneousEdges(bool both=true); void MergeCollinearSegments(Vector a, Vector b); }; @@ -52,7 +72,7 @@ public: SEdge *se; SEdgeLl *next; - static SEdgeLl *Alloc(void); + static SEdgeLl *Alloc(); }; class SKdNodeEdges { @@ -66,21 +86,16 @@ public: static SKdNodeEdges *From(SEdgeList *sel); static SKdNodeEdges *From(SEdgeLl *sell); - static SKdNodeEdges *Alloc(void); + static SKdNodeEdges *Alloc(); int AnyEdgeCrossings(Vector a, Vector b, int cnt, - Vector *pi=NULL, SPointList *spl=NULL); + Vector *pi=NULL, SPointList *spl=NULL) const; }; class SPoint { public: int tag; - enum { - UNKNOWN = 0, - NOT_EAR = 1, - EAR = 2 - }; - int ear; + EarType ear; Vector p; Vector auxv; @@ -90,9 +105,9 @@ class SPointList { public: List l; - void Clear(void); - bool ContainsPoint(Vector pt); - int IndexForPoint(Vector pt); + void Clear(); + bool ContainsPoint(Vector pt) const; + int IndexForPoint(Vector pt) const; void IncrementTagFor(Vector pt); void Add(Vector pt); }; @@ -105,18 +120,19 @@ public: List l; void AddPoint(Vector p); - void MakeEdgesInto(SEdgeList *el); - void Reverse(void); - Vector ComputeNormal(void); - double SignedAreaProjdToNormal(Vector n); - bool IsClockwiseProjdToNormal(Vector n); - bool ContainsPointProjdToNormal(Vector n, Vector p); - void OffsetInto(SContour *dest, double r); - void CopyInto(SContour *dest); - void FindPointWithMinX(void); - Vector AnyEdgeMidpoint(void); - - bool IsEar(int bp, double scaledEps); + void MakeEdgesInto(SEdgeList *el) const; + void Reverse(); + Vector ComputeNormal() const; + double SignedAreaProjdToNormal(Vector n) const; + bool IsClockwiseProjdToNormal(Vector n) const; + bool ContainsPointProjdToNormal(Vector n, Vector p) const; + void OffsetInto(SContour *dest, double r) const; + void CopyInto(SContour *dest) const; + void FindPointWithMinX(); + Vector AnyEdgeMidpoint() const; + + bool IsEmptyTriangle(int ap, int bp, int cp, double scaledEPS) const; + bool IsEar(int bp, double scaledEps) const; bool BridgeToContour(SContour *sc, SEdgeList *el, List *vl); void ClipEarInto(SMesh *m, int bp, double scaledEps); void UvTriangulateInto(SMesh *m, SSurface *srf); @@ -132,36 +148,51 @@ public: List l; Vector normal; - Vector ComputeNormal(void); - void AddEmptyContour(void); - int WindingNumberForPoint(Vector p); - double SignedArea(void); - bool ContainsPoint(Vector p); - void MakeEdgesInto(SEdgeList *el); - void FixContourDirections(void); - void Clear(void); - bool SelfIntersecting(Vector *intersectsAt); - bool IsEmpty(void); - Vector AnyPoint(void); - void OffsetInto(SPolygon *dest, double r); + Vector ComputeNormal() const; + void AddEmptyContour(); + size_t WindingNumberForPoint(Vector p) const; + double SignedArea() const; + bool ContainsPoint(Vector p) const; + void MakeEdgesInto(SEdgeList *el) const; + void FixContourDirections(); + void Clear(); + bool SelfIntersecting(Vector *intersectsAt) const; + bool IsEmpty() const; + Vector AnyPoint() const; + void OffsetInto(SPolygon *dest, double r) const; void UvTriangulateInto(SMesh *m, SSurface *srf); void UvGridTriangulateInto(SMesh *m, SSurface *srf); + void TriangulateInto(SMesh *m) const; + void InverseTransformInto(SPolygon *sp, Vector u, Vector v, Vector n) const; }; class STriangle { public: int tag; STriMeta meta; - Vector a, b, c; - Vector an, bn, cn; + + union { + struct { Vector a, b, c; }; + Vector vertices[3]; + }; + + union { + struct { Vector an, bn, cn; }; + Vector normals[3]; + }; static STriangle From(STriMeta meta, Vector a, Vector b, Vector c); - Vector Normal(void); - void FlipNormal(void); - double MinAltitude(void); - int WindingNumberForPoint(Vector p); - bool ContainsPoint(Vector p); - bool ContainsPointProjd(Vector n, Vector p); + Vector Normal() const; + void FlipNormal(); + double MinAltitude() const; + bool ContainsPoint(Vector p) const; + bool ContainsPointProjd(Vector n, Vector p) const; + STriangle Transform(Vector o, Vector u, Vector v) const; + bool Raytrace(const Vector &rayPoint, const Vector &rayDir, + double *t, Vector *inters) const; + double SignedVolume() const; + double Area() const; + bool IsDegenerate() const; }; class SBsp2 { @@ -177,15 +208,13 @@ public: SBsp2 *more; - enum { POS = 100, NEG = 101, COPLANAR = 200 }; - void InsertTriangleHow(int how, STriangle *tr, SMesh *m, SBsp3 *bsp3); + void InsertTriangleHow(BspClass how, STriangle *tr, SMesh *m, SBsp3 *bsp3); void InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3); - Vector IntersectionWith(Vector a, Vector b); + Vector IntersectionWith(Vector a, Vector b) const; void InsertEdge(SEdge *nedge, Vector nnp, Vector out); - static SBsp2 *InsertOrCreateEdge(SBsp2 *where, SEdge *nedge, Vector nnp, Vector out); - static SBsp2 *Alloc(void); - - void DebugDraw(Vector n, double d); + static SBsp2 *InsertOrCreateEdge(SBsp2 *where, SEdge *nedge, + Vector nnp, Vector out); + static SBsp2 *Alloc(); }; class SBsp3 { @@ -201,25 +230,22 @@ public: SBsp2 *edges; - static SBsp3 *Alloc(void); - static SBsp3 *FromMesh(SMesh *m); + static SBsp3 *Alloc(); + static SBsp3 *FromMesh(const SMesh *m); - Vector IntersectionWith(Vector a, Vector b); + Vector IntersectionWith(Vector a, Vector b) const; - enum { POS = 100, NEG = 101, COPLANAR = 200 }; - void InsertHow(int how, STriangle *str, SMesh *instead); + void InsertHow(BspClass how, STriangle *str, SMesh *instead); void Insert(STriangle *str, SMesh *instead); static SBsp3 *InsertOrCreate(SBsp3 *where, STriangle *str, SMesh *instead); - void InsertConvexHow(int how, STriMeta meta, Vector *vertex, int n, + void InsertConvexHow(BspClass how, STriMeta meta, Vector *vertex, size_t n, SMesh *instead); - SBsp3 *InsertConvex(STriMeta meta, Vector *vertex, int n, SMesh *instead); + SBsp3 *InsertConvex(STriMeta meta, Vector *vertex, size_t n, SMesh *instead); void InsertInPlane(bool pos2, STriangle *tr, SMesh *m); - void GenerateInPaintOrder(SMesh *m); - - void DebugDraw(void); + void GenerateInPaintOrder(SMesh *m) const; }; class SMesh { @@ -227,35 +253,45 @@ public: List l; bool flipNormal; + bool keepInsideOtherShell; bool keepCoplanar; bool atLeastOneDiscarded; bool isTransparent; - void Clear(void); - void AddTriangle(STriangle *st); + void Clear(); + void AddTriangle(const STriangle *st); void AddTriangle(STriMeta meta, Vector a, Vector b, Vector c); - void AddTriangle(STriMeta meta, Vector n, Vector a, Vector b, Vector c); - void DoBounding(Vector v, Vector *vmax, Vector *vmin); - void GetBounding(Vector *vmax, Vector *vmin); + void AddTriangle(STriMeta meta, Vector n, + Vector a, Vector b, Vector c); + void DoBounding(Vector v, Vector *vmax, Vector *vmin) const; + void GetBounding(Vector *vmax, Vector *vmin) const; void Simplify(int start); void AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3); void MakeFromUnionOf(SMesh *a, SMesh *b); void MakeFromDifferenceOf(SMesh *a, SMesh *b); + void MakeFromIntersectionOf(SMesh *a, SMesh *b); void MakeFromCopyOf(SMesh *a); - void MakeFromTransformationOf(SMesh *a, - Vector trans, Quaternion q, double scale); + void MakeFromTransformationOf(SMesh *a, Vector trans, + Quaternion q, double scale); void MakeFromAssemblyOf(SMesh *a, SMesh *b); void MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d); - void MakeCertainEdgesAndOutlinesInto(SEdgeList *sel, SOutlineList *sol, int type); + void MakeOutlinesInto(SOutlineList *sol, EdgeKind type); + + void PrecomputeTransparency(); + void RemoveDegenerateTriangles(); + double CalculateVolume() const; + double CalculateSurfaceArea(const std::vector &faces) const; - bool IsEmpty(void); + bool IsEmpty() const; void RemapFaces(Group *g, int remap); - uint32_t FirstIntersectionWith(Point2d mp); + uint32_t FirstIntersectionWith(Point2d mp) const; + + Vector GetCenterOfMass() const; }; // A linked list of triangles @@ -265,13 +301,15 @@ public: STriangleLl *next; - static STriangleLl *Alloc(void); + static STriangleLl *Alloc(); }; class SOutline { public: int tag; Vector a, b, nl, nr; + + bool IsVisible(Vector projDir) const; }; class SOutlineList { @@ -279,11 +317,10 @@ public: List l; void Clear(); - void AddEdge(Vector a, Vector b, Vector nl, Vector nr); + void AddEdge(Vector a, Vector b, Vector nl, Vector nr, int tag = 0); + void ListTaggedInto(SEdgeList *el, int auxA = 0, int auxB = 0); void MakeFromCopyOf(SOutlineList *ol); - - void FillOutlineTags(Vector projDir); }; class SKdNode { @@ -305,33 +342,79 @@ public: STriangleLl *tris; - static SKdNode *Alloc(void); + static SKdNode *Alloc(); static SKdNode *From(SMesh *m); static SKdNode *From(STriangleLl *tll); void AddTriangle(STriangle *tr); - void MakeMeshInto(SMesh *m); - void ListTrianglesInto(std::vector *tl); - void ClearTags(void); - - void FindEdgeOn(Vector a, Vector b, int cnt, bool coplanarIsInter, EdgeOnInfo *info); - enum { - NAKED_OR_SELF_INTER_EDGES = 100, - SELF_INTER_EDGES = 200, - TURNING_EDGES = 300, - EMPHASIZED_EDGES = 400, - SHARP_EDGES = 500, - }; - void MakeCertainEdgesInto(SEdgeList *sel, int how, bool coplanarIsInter, - bool *inter, bool *leaky, int auxA=0); - void MakeOutlinesInto(SOutlineList *sel); + void MakeMeshInto(SMesh *m) const; + void ListTrianglesInto(std::vector *tl) const; + void ClearTags() const; + + void FindEdgeOn(Vector a, Vector b, int cnt, bool coplanarIsInter, EdgeOnInfo *info) const; + void MakeCertainEdgesInto(SEdgeList *sel, EdgeKind how, bool coplanarIsInter, + bool *inter, bool *leaky, int auxA = 0) const; + void MakeOutlinesInto(SOutlineList *sel, EdgeKind tagKind) const; - void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt, bool removeHidden); - void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr, bool removeHidden); + void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) const; + void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) const; void SnapToMesh(SMesh *m); void SnapToVertex(Vector v, SMesh *extras); }; +class PolylineBuilder { +public: + struct Edge; + + struct Vertex { + Vector pos; + std::vector edges; + + bool GetNext(uint32_t kind, Vertex **next, Edge **nextEdge); + bool GetNext(uint32_t kind, Vector plane, double d, Vertex **next, Edge **nextEdge); + size_t CountEdgesWithTagAndKind(int tag, uint32_t kind) const; + }; + + struct VertexPairHash { + size_t operator()(const std::pair &v) const; + }; + + struct Edge { + Vertex *a; + Vertex *b; + uint32_t kind; + int tag; + + union { + uintptr_t data; + SOutline *outline; + SEdge *edge; + }; + + Vertex *GetOtherVertex(Vertex *v) const; + bool GetStartAndNext(Vertex **start, Vertex **next, bool loop) const; + }; + + std::unordered_map vertices; + std::unordered_map, Edge *, VertexPairHash> edgeMap; + std::vector edges; + + ~PolylineBuilder(); + void Clear(); + + Vertex *AddVertex(const Vector &pos); + Edge *AddEdge(const Vector &p0, const Vector &p1, uint32_t kind, uintptr_t data = 0); + void Generate(std::function const &startFunc, + std::function const &nextFunc, + std::function const &aloneFunc, + std::function const &endFunc = []() {}); + + void MakeFromEdges(const SEdgeList &sel); + void MakeFromOutlines(const SOutlineList &sol); + void GenerateEdges(SEdgeList *sel); + void GenerateOutlines(SOutlineList *sol); +}; + #endif diff --git a/src/polyline.cpp b/src/polyline.cpp new file mode 100644 index 0000000..64aef4e --- /dev/null +++ b/src/polyline.cpp @@ -0,0 +1,243 @@ +//----------------------------------------------------------------------------- +// A helper class to assemble scattered edges into contiguous polylines, +// as nicely as possible. +// +// Copyright 2016 M-Labs Ltd +//----------------------------------------------------------------------------- +#include "solvespace.h" + +bool PolylineBuilder::Vertex::GetNext(uint32_t kind, Vertex **next, Edge **nextEdge) { + auto it = std::find_if(edges.begin(), edges.end(), [&](const Edge *e) { + return e->tag == 0 && e->kind == kind; + }); + + if(it != edges.end()) { + (*it)->tag = 1; + *next = (*it)->GetOtherVertex(this); + *nextEdge = *it; + return true; + } + + return false; +} + +bool PolylineBuilder::Vertex::GetNext(uint32_t kind, Vector plane, double dist, + Vertex **next, Edge **nextEdge) { + Edge *best = NULL; + double minD = VERY_POSITIVE; + for(Edge *e : edges) { + if(e->tag != 0) continue; + if(e->kind != kind) continue; + + // We choose the best next edge with minimal distance from the current plane + Vector nextPos = e->GetOtherVertex(this)->pos; + double curD = fabs(plane.Dot(nextPos) - dist); + if(best != NULL && curD > minD) continue; + best = e; + minD = curD; + } + + if(best != NULL) { + best->tag = 1; + *next = best->GetOtherVertex(this); + *nextEdge = best; + return true; + } + + return false; +} + + +size_t PolylineBuilder::Vertex::CountEdgesWithTagAndKind(int tag, uint32_t kind) const { + return std::count_if(edges.begin(), edges.end(), [&](const Edge *e) { + return e->tag == tag && e->kind == kind; + }); +} + +PolylineBuilder::Vertex *PolylineBuilder::Edge::GetOtherVertex(PolylineBuilder::Vertex *v) const { + if(a == v) return b; + if(b == v) return a; + return NULL; +} + +size_t PolylineBuilder::VertexPairHash::operator()(const std::pair &v) const { + return ((uintptr_t)v.first / sizeof(Vertex)) ^ + ((uintptr_t)v.second / sizeof(Vertex)); +} + +bool PolylineBuilder::Edge::GetStartAndNext(PolylineBuilder::Vertex **start, + PolylineBuilder::Vertex **next, bool loop) const { + size_t numA = a->CountEdgesWithTagAndKind(0, kind); + size_t numB = b->CountEdgesWithTagAndKind(0, kind); + + if((numA == 1 && numB > 1) || (loop && numA > 1 && numB > 1)) { + *start = a; + *next = b; + return true; + } + + if(numA > 1 && numB == 1) { + *start = b; + *next = a; + return true; + } + + return false; +} + +PolylineBuilder::~PolylineBuilder() { + Clear(); +} + +void PolylineBuilder::Clear() { + for(Edge *e : edges) { + delete e; + } + edges.clear(); + + for(auto &v : vertices) { + delete v.second; + } + vertices.clear(); +} + +PolylineBuilder::Vertex *PolylineBuilder::AddVertex(const Vector &pos) { + auto it = vertices.find(pos); + if(it != vertices.end()) { + return it->second; + } + + Vertex *result = new Vertex; + result->pos = pos; + vertices.emplace(pos, result); + + return result; +} + +PolylineBuilder::Edge *PolylineBuilder::AddEdge(const Vector &p0, const Vector &p1, + uint32_t kind, uintptr_t data) { + Vertex *v0 = AddVertex(p0); + Vertex *v1 = AddVertex(p1); + if(v0 == v1) return NULL; + + auto it = edgeMap.find(std::make_pair(v0, v1)); + if(it != edgeMap.end()) { + return it->second; + } + + PolylineBuilder::Edge *edge = new PolylineBuilder::Edge {}; + edge->a = v0; + edge->b = v1; + edge->kind = kind; + edge->tag = 0; + edge->data = data; + edges.push_back(edge); + edgeMap.emplace(std::make_pair(v0, v1), edge); + + v0->edges.push_back(edge); + v1->edges.push_back(edge); + + return edge; +} + +void PolylineBuilder::Generate( + std::function const &startFunc, + std::function const &nextFunc, + std::function const &aloneFunc, std::function const &endFunc) { + bool found; + bool loop = false; + do { + found = false; + for(PolylineBuilder::Edge *e : edges) { + if(e->tag != 0) continue; + + Vertex *start; + Vertex *next; + if(!e->GetStartAndNext(&start, &next, loop)) continue; + + Vector startPos = start->pos; + Vector nextPos = next->pos; + found = true; + e->tag = 1; + + startFunc(start, next, e); + + Edge *nextEdge; + if(next->GetNext(e->kind, &next, &nextEdge)) { + Vector plane = nextPos.Minus(startPos).Cross(next->pos.Minus(startPos)); + double dist = plane.Dot(startPos); + nextFunc(next, nextEdge); + while(next->GetNext(e->kind, plane, dist, &next, &nextEdge)) { + nextFunc(next, nextEdge); + } + } + + endFunc(); + } + + if(!found && !loop) { + loop = true; + found = true; + } + } while(found); + + for(PolylineBuilder::Edge *e : edges) { + if(e->tag != 0) continue; + aloneFunc(e); + } +} + +void PolylineBuilder::MakeFromEdges(const SEdgeList &sel) { + for(const SEdge &se : sel.l) { + AddEdge(se.a, se.b, (uint32_t)se.auxA, reinterpret_cast(&se)); + } +} + +void PolylineBuilder::MakeFromOutlines(const SOutlineList &ol) { + for(const SOutline &so : ol.l) { + // Use outline tag as kind, so that emphasized and contour outlines + // would not be composed together. + AddEdge(so.a, so.b, (uint32_t)so.tag, reinterpret_cast(&so)); + } +} + +void PolylineBuilder::GenerateEdges(SEdgeList *sel) { + Vector prev; + auto startFunc = [&](Vertex *start, Vertex *next, Edge *e) { + sel->AddEdge(start->pos, next->pos, e->kind); + prev = next->pos; + }; + + auto nextFunc = [&](Vertex *next, Edge *e) { + sel->AddEdge(prev, next->pos, e->kind); + prev = next->pos; + }; + + auto aloneFunc = [&](Edge *e) { + sel->AddEdge(e->a->pos, e->b->pos, e->kind); + }; + + Generate(startFunc, nextFunc, aloneFunc); +} + +void PolylineBuilder::GenerateOutlines(SOutlineList *sol) { + Vector prev; + auto startFunc = [&](Vertex *start, Vertex *next, Edge *e) { + SOutline *so = e->outline; + sol->AddEdge(start->pos, next->pos, so->nl, so->nr, so->tag); + prev = next->pos; + }; + + auto nextFunc = [&](Vertex *next, Edge *e) { + SOutline *so = e->outline; + sol->AddEdge(prev, next->pos, so->nl, so->nr, so->tag); + prev = next->pos; + }; + + auto aloneFunc = [&](Edge *e) { + SOutline *so = e->outline; + sol->AddEdge(so->a, so->b, so->nl, so->nr, so->tag); + }; + + Generate(startFunc, nextFunc, aloneFunc); +} diff --git a/src/render/gl3shader.cpp b/src/render/gl3shader.cpp new file mode 100644 index 0000000..a309046 --- /dev/null +++ b/src/render/gl3shader.cpp @@ -0,0 +1,1071 @@ +//----------------------------------------------------------------------------- +// OpenGL ES 2.0 and OpenGL 3.0 shader interface. +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +#include "solvespace.h" +#include "gl3shader.h" + +namespace SolveSpace { + +//----------------------------------------------------------------------------- +// Floating point data structures +//----------------------------------------------------------------------------- + +Vector2f Vector2f::From(float x, float y) { + return { x, y }; +} + +Vector2f Vector2f::From(double x, double y) { + return { (float)x, (float)y }; +} + +Vector2f Vector2f::FromInt(uint32_t x, uint32_t y) { + return { (float)x, (float)y }; +} + +Vector3f Vector3f::From(float x, float y, float z) { + return { x, y, z }; +} + +Vector3f Vector3f::From(const Vector &v) { + return { (float)v.x, (float)v.y, (float)v.z }; +} + +Vector3f Vector3f::From(const RgbaColor &c) { + return { c.redF(), c.greenF(), c.blueF() }; +} + +Vector4f Vector4f::From(float x, float y, float z, float w) { + return { x, y, z, w }; +} + +Vector4f Vector4f::From(const Vector &v, float w) { + return { (float)v.x, (float)v.y, (float)v.z, w }; +} + +Vector4f Vector4f::FromInt(uint32_t x, uint32_t y, uint32_t z, uint32_t w) { + return { (float)x, (float)y, (float)z, (float)w }; +} + +Vector4f Vector4f::From(const RgbaColor &c) { + return { c.redF(), c.greenF(), c.blueF(), c.alphaF() }; +} + +//----------------------------------------------------------------------------- +// Shader manipulation +//----------------------------------------------------------------------------- + +static GLuint CompileShader(const std::string &res, GLenum type) { + size_t size; + const char *resData = (const char *)Platform::LoadResource(res, &size); + + // Sigh, here we go... We want to deploy to four platforms: Linux, Windows, OS X, mobile+web. + // These platforms are basically disjunctive in the OpenGL versions and profiles that they + // support: mobile+web support GLES2, Windows can only be guaranteed to support GL1 without + // vendor's drivers installed but supports D3D9+ natively, Linux supports GL3.2+ and/or + // GLES2+ depending on whether we run on X11 or Wayland, and OS X supports either a legacy + // profile or a GL3.2 core profile or (on 10.9+) a GL4.1 core profile. + // The platforms barely have a common subset of features: + // * mobile+web and Windows (D3D9 through ANGLE) are strictly GLES2/GLSL1.0; + // * OS X legacy compatibility profile has GLSL1.2 only shaders, and GL3.2 core profile + // that has GLSL1.0 shaders compatible with GLES2 makes mandatory the use of vertex array + // objects, which cannot be used in GLES2 at all; similarly GL3.2 core has GL_RED but not + // GL_ALPHA whereas GLES2 has GL_ALPHA but not GL_RED. + // * GTK does not work on anything prior to GL3.0/GLES2.0; it does not permit explicitly + // asking for a compatibility profile, i.e. you can only ask for 3.2+; and it does not + // permit asking for a GLES profile prior to GTK 3.22, which will get into Ubuntu + // no earlier than late 2017. This is despite the fact that if only GTK defaulted + // to the compatibility profile, everything would have just worked as Mesa is + // very permissive. + // While we're at it, let's remember that GLES2 has *only* glDepthRangef, GL3.2 has *only* + // glDepthRange, and GL4.1+ has both glDepthRangef and glDepthRange. Also, that GLSL1.0 + // makes `precision highp float;` mandatory in fragment shaders, and GLSL1.2 removes + // the `precision` keyword entirely, because that's clearly how minor versions work. + // Christ, what a trash fire. + + const char *prelude; +#if defined(HAVE_GLES) + prelude = R"( +#version 100 +#define TEX_ALPHA a +precision highp float; +)"; +#else + prelude = R"( +#version 120 +#define TEX_ALPHA r +)"; +#endif + std::string src(resData, size); + src = prelude + src; + + GLuint shader = glCreateShader(type); + ssassert(shader != 0, "glCreateShader failed"); + + const GLint glSize[] = { (int)src.length() }; + const GLchar* glSource[] = { src.c_str() }; + glShaderSource(shader, 1, glSource, glSize); + glCompileShader(shader); + + GLint infoLen; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + if(infoLen > 1) { + std::string infoStr(infoLen, '\0'); + glGetShaderInfoLog(shader, infoLen, NULL, &infoStr[0]); + dbp(infoStr.c_str()); + } + + GLint compiled; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if(!compiled) { + dbp("Failed to compile shader:\n" + "----8<----8<----8<----8<----8<----\n" + "%s\n" + "----8<----8<----8<----8<----8<----\n", + src.c_str()); + } + ssassert(compiled, "Cannot compile shader"); + + return shader; +} + +void Shader::Init(const std::string &vertexRes, const std::string &fragmentRes, + const std::vector > &locations) { + GLuint vert = CompileShader(vertexRes, GL_VERTEX_SHADER); + GLuint frag = CompileShader(fragmentRes, GL_FRAGMENT_SHADER); + + program = glCreateProgram(); + ssassert(program != 0, "glCreateProgram failed"); + + glAttachShader(program, vert); + glAttachShader(program, frag); + for(const auto &l : locations) { + glBindAttribLocation(program, l.first, l.second.c_str()); + } + glLinkProgram(program); + + GLint infoLen; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen); + if(infoLen > 1) { + std::string infoStr(infoLen, '\0'); + glGetProgramInfoLog(program, infoLen, NULL, &infoStr[0]); + dbp(infoStr.c_str()); + } + + GLint linked; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + ssassert(linked, "Cannot link shader"); +} + +void Shader::Clear() { + glDeleteProgram(program); +} + +void Shader::SetUniformMatrix(const char *name, const double *md) { + Enable(); + float mf[16]; + for(int i = 0; i < 16; i++) mf[i] = (float)md[i]; + glUniformMatrix4fv(glGetUniformLocation(program, name), 1, false, mf); +} + +void Shader::SetUniformVector(const char *name, const Vector &v) { + Enable(); + glUniform3f(glGetUniformLocation(program, name), (float)v.x, (float)v.y, (float)v.z); +} + +void Shader::SetUniformVector(const char *name, const Vector4f &v) { + Enable(); + glUniform4f(glGetUniformLocation(program, name), v.x, v.y, v.z, v.w); +} + +void Shader::SetUniformColor(const char *name, RgbaColor c) { + Enable(); + glUniform4f(glGetUniformLocation(program, name), c.redF(), c.greenF(), c.blueF(), c.alphaF()); +} + +void Shader::SetUniformFloat(const char *name, float v) { + Enable(); + glUniform1f(glGetUniformLocation(program, name), v); +} + +void Shader::SetUniformInt(const char *name, GLint v) { + Enable(); + glUniform1i(glGetUniformLocation(program, name), v); +} + +void Shader::SetUniformTextureUnit(const char *name, GLint index) { + Enable(); + glUniform1i(glGetUniformLocation(program, name), index); +} + +void Shader::Enable() const { + glUseProgram(program); +} + +void Shader::Disable() const { + glUseProgram(0); +} + +//----------------------------------------------------------------------------- +// Mesh rendering +//----------------------------------------------------------------------------- + +void MeshRenderer::Init() { + lightShader.Init( + "shaders/mesh.vert", "shaders/mesh.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_NOR, "nor" }, + { ATTRIB_COL, "col" }, + } + ); + + fillShader.Init( + "shaders/mesh_fill.vert", "shaders/mesh_fill.frag", + { + { ATTRIB_POS, "pos" }, + } + ); + fillShader.SetUniformTextureUnit("texture_", 0); + + selectedShader = &lightShader; +} + +void MeshRenderer::Clear() { + lightShader.Clear(); + fillShader.Clear(); +} + +MeshRenderer::Handle MeshRenderer::Add(const SMesh &m, bool dynamic) { + Handle handle; + glGenBuffers(1, &handle.vertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + + MeshVertex *vertices = new MeshVertex[m.l.n * 3]; + for(int i = 0; i < m.l.n; i++) { + const STriangle &t = m.l[i]; + vertices[i * 3 + 0].pos = Vector3f::From(t.a); + vertices[i * 3 + 1].pos = Vector3f::From(t.b); + vertices[i * 3 + 2].pos = Vector3f::From(t.c); + + if(t.an.EqualsExactly(Vector::From(0, 0, 0))) { + Vector3f normal = Vector3f::From(t.Normal()); + vertices[i * 3 + 0].nor = normal; + vertices[i * 3 + 1].nor = normal; + vertices[i * 3 + 2].nor = normal; + } else { + vertices[i * 3 + 0].nor = Vector3f::From(t.an); + vertices[i * 3 + 1].nor = Vector3f::From(t.bn); + vertices[i * 3 + 2].nor = Vector3f::From(t.cn); + } + + for(int j = 0; j < 3; j++) { + vertices[i * 3 + j].col = Vector4f::From(t.meta.color); + } + + } + glBufferData(GL_ARRAY_BUFFER, m.l.n * 3 * sizeof(MeshVertex), + vertices, dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); + handle.size = m.l.n * 3; + delete []vertices; + + return handle; +} + +void MeshRenderer::Remove(const MeshRenderer::Handle &handle) { + glDeleteBuffers(1, &handle.vertexBuffer); +} + +void MeshRenderer::Draw(const MeshRenderer::Handle &handle, + bool useColors, RgbaColor overrideColor) { + if(handle.size == 0) return; + + selectedShader->Enable(); + + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + + glEnableVertexAttribArray(ATTRIB_POS); + glVertexAttribPointer(ATTRIB_POS, 3, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), + (void *)offsetof(MeshVertex, pos)); + + if(selectedShader == &lightShader) { + glEnableVertexAttribArray(ATTRIB_NOR); + glVertexAttribPointer(ATTRIB_NOR, 3, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), + (void *)offsetof(MeshVertex, nor)); + if(useColors) { + glEnableVertexAttribArray(ATTRIB_COL); + glVertexAttribPointer(ATTRIB_COL, 4, GL_FLOAT, GL_FALSE, sizeof(MeshVertex), + (void *)offsetof(MeshVertex, col)); + } else { + glVertexAttrib4f(ATTRIB_COL, overrideColor.redF(), overrideColor.greenF(), overrideColor.blueF(), overrideColor.alphaF()); + } + } + + glDrawArrays(GL_TRIANGLES, 0, handle.size); + + glDisableVertexAttribArray(ATTRIB_POS); + if(selectedShader == &lightShader) { + glDisableVertexAttribArray(ATTRIB_NOR); + if(useColors) glDisableVertexAttribArray(ATTRIB_COL); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + + selectedShader->Disable(); +} + +void MeshRenderer::Draw(const SMesh &mesh, bool useColors, RgbaColor overrideColor) { + Handle handle = Add(mesh, /*dynamic=*/true); + Draw(handle, useColors, overrideColor); + Remove(handle); +} + +void MeshRenderer::SetModelview(double *matrix) { + lightShader.SetUniformMatrix("modelview", matrix); + fillShader.SetUniformMatrix("modelview", matrix); +} + +void MeshRenderer::SetProjection(double *matrix) { + lightShader.SetUniformMatrix("projection", matrix); + fillShader.SetUniformMatrix("projection", matrix); +} + +void MeshRenderer::UseShaded(const Lighting &lighting) { + Vector dir0 = lighting.lightDirection[0]; + Vector dir1 = lighting.lightDirection[1]; + dir0.z = -dir0.z; + dir1.z = -dir1.z; + + lightShader.SetUniformVector("lightDir0", dir0); + lightShader.SetUniformFloat("lightInt0", (float)lighting.lightIntensity[0]); + lightShader.SetUniformVector("lightDir1", dir1); + lightShader.SetUniformFloat("lightInt1", (float)lighting.lightIntensity[1]); + lightShader.SetUniformFloat("ambient", (float)lighting.ambientIntensity); + selectedShader = &lightShader; +} + +void MeshRenderer::UseFilled(const Canvas::Fill &fill) { + fillShader.SetUniformColor("color", fill.color); + selectedShader = &fillShader; +} + +//----------------------------------------------------------------------------- +// Arrangement of stipple patterns into textures +//----------------------------------------------------------------------------- + +static double Frac(double x) { + return x - floor(x); +} + +static RgbaColor EncodeLengthAsFloat(double v) { + v = max(0.0, min(1.0, v)); + double er = v; + double eg = Frac(255.0 * v); + double eb = Frac(65025.0 * v); + double ea = Frac(160581375.0 * v); + + double r = er - eg / 255.0; + double g = eg - eb / 255.0; + double b = eb - ea / 255.0; + return RgbaColor::From((int)floor( r * 255.0 + 0.5), + (int)floor( g * 255.0 + 0.5), + (int)floor( b * 255.0 + 0.5), + (int)floor(ea * 255.0 + 0.5)); +} + +GLuint Generate(const std::vector &pattern) { + double patternLen = 0.0; + for(double s : pattern) { + patternLen += s; + } + + GLuint texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + + GLint size; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &size); + size /= 2; + + RgbaColor *textureData = new RgbaColor[size]; + int mipCount = (int)log2(size) + 1; + for(int mip = 0; mip < mipCount; mip++) { + int dashI = 0; + double dashT = 0.0; + for(int i = 0; i < size; i++) { + if(pattern.empty()) { + textureData[i] = EncodeLengthAsFloat(0.0); + continue; + } + + double t = (double)i / (double)(size - 1); + while(t - LENGTH_EPS > dashT + pattern[dashI] / patternLen) { + dashT += pattern[dashI] / patternLen; + dashI++; + } + double dashW = pattern[dashI] / patternLen; + if(dashI % 2 == 0) { + textureData[i] = EncodeLengthAsFloat(0.0); + } else { + double value; + if(t - dashT < pattern[dashI] / patternLen / 2.0) { + value = t - dashT; + } else { + value = dashT + dashW - t; + } + value = value * patternLen; + textureData[i] = EncodeLengthAsFloat(value); + } + } + glTexImage2D(GL_TEXTURE_2D, mip, GL_RGBA, size, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, + textureData); + size /= 2; + } + delete []textureData; + + return texture; +} + +void StippleAtlas::Init() { + for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) { + patterns.push_back(Generate(StipplePatternDashes((StipplePattern)i))); + } +} + +void StippleAtlas::Clear() { + for(GLuint p : patterns) { + glDeleteTextures(1, &p); + } +} + +GLint StippleAtlas::GetTexture(StipplePattern pattern) const { + return patterns[(uint32_t)pattern]; +} + +double StippleAtlas::GetLength(StipplePattern pattern) const { + if(pattern == StipplePattern::CONTINUOUS) { + return 1.0; + } + return StipplePatternLength(pattern); +} + +//----------------------------------------------------------------------------- +// Edge rendering +//----------------------------------------------------------------------------- + +void EdgeRenderer::Init(const StippleAtlas *a) { + atlas = a; + shader.Init( + "shaders/edge.vert", "shaders/edge.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_LOC, "loc" }, + { ATTRIB_TAN, "tgt" } + } + ); +} + +void EdgeRenderer::Clear() { + shader.Clear(); +} + +EdgeRenderer::Handle EdgeRenderer::Add(const SEdgeList &edges, bool dynamic) { + Handle handle; + glGenBuffers(1, &handle.vertexBuffer); + glGenBuffers(1, &handle.indexBuffer); + + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle.indexBuffer); + + EdgeVertex *vertices = new EdgeVertex[edges.l.n * 8]; + uint32_t *indices = new uint32_t[edges.l.n * 6 * 3]; + double phase = 0.0; + uint32_t curVertex = 0; + uint32_t curIndex = 0; + for(int i = 0; i < edges.l.n; i++) { + const SEdge &curr = edges.l[i]; + const SEdge &next = edges.l[(i + 1) % edges.l.n]; + + // 3d positions + Vector3f a = Vector3f::From(curr.a); + Vector3f b = Vector3f::From(curr.b); + + // tangent + Vector3f tan = Vector3f::From(curr.b.Minus(curr.a)); + + // length + double len = curr.b.Minus(curr.a).Magnitude(); + + // make line start cap + for(int j = 0; j < 2; j++) { + vertices[curVertex + j].pos = a; + vertices[curVertex + j].tan = tan; + } + vertices[curVertex + 0].loc = Vector3f::From(-1.0f, -1.0f, float(phase)); + vertices[curVertex + 1].loc = Vector3f::From(-1.0f, +1.0f, float(phase)); + + indices[curIndex++] = curVertex + 0; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 3; + + curVertex += 2; + + // make line body + vertices[curVertex + 0].pos = a; + vertices[curVertex + 1].pos = a; + vertices[curVertex + 2].pos = b; + vertices[curVertex + 3].pos = b; + + for(int j = 0; j < 4; j++) { + vertices[curVertex + j].tan = tan; + } + + vertices[curVertex + 0].loc = Vector3f::From( 0.0f, -1.0f, float(phase)); + vertices[curVertex + 1].loc = Vector3f::From( 0.0f, +1.0f, float(phase)); + vertices[curVertex + 2].loc = Vector3f::From( 0.0f, +1.0f, float(phase + len)); + vertices[curVertex + 3].loc = Vector3f::From( 0.0f, -1.0f, float(phase + len)); + + indices[curIndex++] = curVertex + 0; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 0; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 3; + + curVertex += 4; + + // make line end cap + for(int j = 0; j < 2; j++) { + vertices[curVertex + j].pos = b; + vertices[curVertex + j].tan = tan; + } + + vertices[curVertex + 0].loc = Vector3f::From(+1.0, +1.0, float(phase + len)); + vertices[curVertex + 1].loc = Vector3f::From(+1.0, -1.0, float(phase + len)); + + indices[curIndex++] = curVertex - 2; + indices[curIndex++] = curVertex - 1; + indices[curIndex++] = curVertex; + indices[curIndex++] = curVertex - 1; + indices[curIndex++] = curVertex; + indices[curIndex++] = curVertex + 1; + + curVertex += 2; + + // phase stitching + if(curr.a.EqualsExactly(next.a) || + curr.a.EqualsExactly(next.b) || + curr.b.EqualsExactly(next.a) || + curr.b.EqualsExactly(next.b)) + { + phase += len; + } else { + phase = 0.0; + } + } + handle.size = curIndex; + GLenum mode = dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW; + glBufferData(GL_ARRAY_BUFFER, curVertex * sizeof(EdgeVertex), vertices, mode); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, curIndex * sizeof(uint32_t), indices, mode); + delete []vertices; + delete []indices; + + return handle; +} + +void EdgeRenderer::Remove(const EdgeRenderer::Handle &handle) { + glDeleteBuffers(1, &handle.vertexBuffer); + glDeleteBuffers(1, &handle.indexBuffer); +} + +void EdgeRenderer::Draw(const EdgeRenderer::Handle &handle) { + if(handle.size == 0) return; + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, atlas->GetTexture(pattern)); + shader.SetUniformTextureUnit("pattern", 1); + shader.SetUniformFloat("patternLen", (float)atlas->GetLength(pattern)); + + shader.Enable(); + + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle.indexBuffer); + + glEnableVertexAttribArray(ATTRIB_POS); + glEnableVertexAttribArray(ATTRIB_LOC); + glEnableVertexAttribArray(ATTRIB_TAN); + + glVertexAttribPointer(ATTRIB_POS, 3, GL_FLOAT, GL_FALSE, sizeof(EdgeVertex), (void *)offsetof(EdgeVertex, pos)); + glVertexAttribPointer(ATTRIB_LOC, 3, GL_FLOAT, GL_FALSE, sizeof(EdgeVertex), (void *)offsetof(EdgeVertex, loc)); + glVertexAttribPointer(ATTRIB_TAN, 3, GL_FLOAT, GL_FALSE, sizeof(EdgeVertex), (void *)offsetof(EdgeVertex, tan)); + glDrawElements(GL_TRIANGLES, handle.size, GL_UNSIGNED_INT, NULL); + + glDisableVertexAttribArray(ATTRIB_POS); + glDisableVertexAttribArray(ATTRIB_LOC); + glDisableVertexAttribArray(ATTRIB_TAN); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + shader.Disable(); +} + +void EdgeRenderer::Draw(const SEdgeList &edges) { + Handle handle = Add(edges, /*dynamic=*/true); + Draw(handle); + Remove(handle); +} + +void EdgeRenderer::SetModelview(const double *matrix) { + shader.SetUniformMatrix("modelview", matrix); +} + +void EdgeRenderer::SetProjection(const double *matrix) { + shader.SetUniformMatrix("projection", matrix); +} + +void EdgeRenderer::SetStroke(const Canvas::Stroke &stroke, double pixel) { + double unitScale = stroke.unit == Canvas::Unit::PX ? pixel : 1.0; + shader.SetUniformFloat("width", float(stroke.width * unitScale / 2.0)); + shader.SetUniformColor("color", stroke.color); + shader.SetUniformFloat("patternScale", float(stroke.stippleScale * unitScale * 2.0)); + shader.SetUniformFloat("pixel", (float)pixel); + pattern = stroke.stipplePattern; +} + +//----------------------------------------------------------------------------- +// Outline rendering +//----------------------------------------------------------------------------- + +void OutlineRenderer::Init(const StippleAtlas *a) { + atlas = a; + shader.Init( + "shaders/outline.vert", "shaders/edge.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_LOC, "loc" }, + { ATTRIB_TAN, "tgt" }, + { ATTRIB_NOL, "nol" }, + { ATTRIB_NOR, "nor" } + } + ); +} + +void OutlineRenderer::Clear() { + shader.Clear(); +} + +OutlineRenderer::Handle OutlineRenderer::Add(const SOutlineList &outlines, bool dynamic) { + Handle handle; + glGenBuffers(1, &handle.vertexBuffer); + glGenBuffers(1, &handle.indexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle.indexBuffer); + + OutlineVertex *vertices = new OutlineVertex[outlines.l.n * 8]; + uint32_t *indices = new uint32_t[outlines.l.n * 6 * 3]; + double phase = 0.0; + uint32_t curVertex = 0; + uint32_t curIndex = 0; + + for(int i = 0; i < outlines.l.n; i++) { + const SOutline &curr = outlines.l[i]; + const SOutline &next = outlines.l[(i + 1) % outlines.l.n]; + + // 3d positions + Vector3f a = Vector3f::From(curr.a); + Vector3f b = Vector3f::From(curr.b); + Vector3f nl = Vector3f::From(curr.nl); + Vector3f nr = Vector3f::From(curr.nr); + + // tangent + Vector3f tan = Vector3f::From(curr.b.Minus(curr.a)); + + // length + double len = curr.b.Minus(curr.a).Magnitude(); + float tag = (float)curr.tag; + + // make line start cap + for(int j = 0; j < 2; j++) { + vertices[curVertex + j].pos = a; + vertices[curVertex + j].nol = nl; + vertices[curVertex + j].nor = nr; + vertices[curVertex + j].tan = tan; + } + + vertices[curVertex + 0].loc = Vector4f::From(-1.0f, -1.0f, float(phase), (float)tag); + vertices[curVertex + 1].loc = Vector4f::From(-1.0f, +1.0f, float(phase), (float)tag); + + indices[curIndex++] = curVertex; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 3; + + curVertex += 2; + + // make line body + vertices[curVertex + 0].pos = a; + vertices[curVertex + 1].pos = a; + vertices[curVertex + 2].pos = b; + vertices[curVertex + 3].pos = b; + + for(int j = 0; j < 4; j++) { + vertices[curVertex + j].nol = nl; + vertices[curVertex + j].nor = nr; + vertices[curVertex + j].tan = tan; + } + + vertices[curVertex + 0].loc = Vector4f::From( 0.0f, -1.0f, float(phase), (float)tag); + vertices[curVertex + 1].loc = Vector4f::From( 0.0f, +1.0f, float(phase), (float)tag); + vertices[curVertex + 2].loc = Vector4f::From( 0.0f, +1.0f, + float(phase + len), (float)tag); + vertices[curVertex + 3].loc = Vector4f::From( 0.0f, -1.0f, + float(phase + len), (float)tag); + + indices[curIndex++] = curVertex + 0; + indices[curIndex++] = curVertex + 1; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 0; + indices[curIndex++] = curVertex + 2; + indices[curIndex++] = curVertex + 3; + + curVertex += 4; + + // make line end cap + for(int j = 0; j < 2; j++) { + vertices[curVertex + j].pos = b; + vertices[curVertex + j].nol = nl; + vertices[curVertex + j].nor = nr; + vertices[curVertex + j].tan = tan; + } + + vertices[curVertex + 0].loc = Vector4f::From(+1.0f, +1.0f, float(phase + len), (float)tag); + vertices[curVertex + 1].loc = Vector4f::From(+1.0f, -1.0f, float(phase + len), (float)tag); + + indices[curIndex++] = curVertex - 2; + indices[curIndex++] = curVertex - 1; + indices[curIndex++] = curVertex; + indices[curIndex++] = curVertex - 1; + indices[curIndex++] = curVertex; + indices[curIndex++] = curVertex + 1; + + curVertex += 2; + + // phase stitching + if(curr.a.EqualsExactly(next.a) || + curr.a.EqualsExactly(next.b) || + curr.b.EqualsExactly(next.a) || + curr.b.EqualsExactly(next.b)) + { + phase += len; + } else { + phase = 0.0; + } + } + handle.size = curIndex; + GLenum mode = dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW; + glBufferData(GL_ARRAY_BUFFER, curVertex * sizeof(OutlineVertex), vertices, mode); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, curIndex * sizeof(uint32_t), indices, mode); + + delete []vertices; + delete []indices; + return handle; +} + +void OutlineRenderer::Remove(const OutlineRenderer::Handle &handle) { + glDeleteBuffers(1, &handle.vertexBuffer); + glDeleteBuffers(1, &handle.indexBuffer); +} + +void OutlineRenderer::Draw(const OutlineRenderer::Handle &handle, Canvas::DrawOutlinesAs mode) { + if(handle.size == 0) return; + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, atlas->GetTexture(pattern)); + shader.SetUniformTextureUnit("pattern", 1); + shader.SetUniformFloat("patternLen", (float)atlas->GetLength(pattern)); + shader.SetUniformInt("mode", (GLint)mode); + + shader.Enable(); + + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle.indexBuffer); + + glEnableVertexAttribArray(ATTRIB_POS); + glEnableVertexAttribArray(ATTRIB_LOC); + glEnableVertexAttribArray(ATTRIB_TAN); + glEnableVertexAttribArray(ATTRIB_NOL); + glEnableVertexAttribArray(ATTRIB_NOR); + + glVertexAttribPointer(ATTRIB_POS, 3, GL_FLOAT, GL_FALSE, sizeof(OutlineVertex), + (void *)offsetof(OutlineVertex, pos)); + glVertexAttribPointer(ATTRIB_LOC, 4, GL_FLOAT, GL_FALSE, sizeof(OutlineVertex), + (void *)offsetof(OutlineVertex, loc)); + glVertexAttribPointer(ATTRIB_TAN, 3, GL_FLOAT, GL_FALSE, sizeof(OutlineVertex), + (void *)offsetof(OutlineVertex, tan)); + glVertexAttribPointer(ATTRIB_NOL, 3, GL_FLOAT, GL_FALSE, sizeof(OutlineVertex), + (void *)offsetof(OutlineVertex, nol)); + glVertexAttribPointer(ATTRIB_NOR, 3, GL_FLOAT, GL_FALSE, sizeof(OutlineVertex), + (void *)offsetof(OutlineVertex, nor)); + glDrawElements(GL_TRIANGLES, handle.size, GL_UNSIGNED_INT, NULL); + + glDisableVertexAttribArray(ATTRIB_POS); + glDisableVertexAttribArray(ATTRIB_LOC); + glDisableVertexAttribArray(ATTRIB_TAN); + glDisableVertexAttribArray(ATTRIB_NOL); + glDisableVertexAttribArray(ATTRIB_NOR); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + shader.Disable(); +} + +void OutlineRenderer::Draw(const SOutlineList &outlines, Canvas::DrawOutlinesAs drawAs) { + Handle handle = Add(outlines, /*dynamic=*/true); + Draw(handle, drawAs); + Remove(handle); +} + +void OutlineRenderer::SetModelview(const double *matrix) { + shader.SetUniformMatrix("modelview", matrix); +} + +void OutlineRenderer::SetProjection(const double *matrix) { + shader.SetUniformMatrix("projection", matrix); +} + +void OutlineRenderer::SetStroke(const Canvas::Stroke &stroke, double pixel) { + double unitScale = (stroke.unit == Canvas::Unit::PX) ? pixel : 1.0; + shader.SetUniformFloat("width", (float)(stroke.width * unitScale / 2.0)); + shader.SetUniformColor("color", stroke.color); + shader.SetUniformFloat("patternScale", (float)(stroke.stippleScale * unitScale * 2.0)); + shader.SetUniformFloat("pixel", (float)pixel); + pattern = stroke.stipplePattern; +} + +//----------------------------------------------------------------------------- +// Indexed mesh storage +//----------------------------------------------------------------------------- + +void SIndexedMesh::AddPoint(const Vector &p) { + uint32_t vstart = vertices.size(); + vertices.resize(vertices.size() + 4); + + vertices[vstart + 0].pos = Vector3f::From(p); + vertices[vstart + 0].tex = Vector2f::From(-1.0f, -1.0f); + vertices[vstart + 1].pos = Vector3f::From(p); + vertices[vstart + 1].tex = Vector2f::From(+1.0f, -1.0f); + vertices[vstart + 2].pos = Vector3f::From(p); + vertices[vstart + 2].tex = Vector2f::From(+1.0f, +1.0f); + vertices[vstart + 3].pos = Vector3f::From(p); + vertices[vstart + 3].tex = Vector2f::From(-1.0f, +1.0f); + + size_t istart = indices.size(); + indices.resize(indices.size() + 6); + + indices[istart + 0] = vstart + 0; + indices[istart + 1] = vstart + 1; + indices[istart + 2] = vstart + 2; + indices[istart + 3] = vstart + 0; + indices[istart + 4] = vstart + 2; + indices[istart + 5] = vstart + 3; +} + +void SIndexedMesh::AddQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d) { + uint32_t vstart = vertices.size(); + vertices.resize(vertices.size() + 4); + + vertices[vstart + 0].pos = Vector3f::From(a); + vertices[vstart + 1].pos = Vector3f::From(b); + vertices[vstart + 2].pos = Vector3f::From(c); + vertices[vstart + 3].pos = Vector3f::From(d); + + size_t istart = indices.size(); + indices.resize(indices.size() + 6); + + indices[istart + 0] = vstart + 0; + indices[istart + 1] = vstart + 1; + indices[istart + 2] = vstart + 2; + indices[istart + 3] = vstart + 0; + indices[istart + 4] = vstart + 2; + indices[istart + 5] = vstart + 3; +} + +void SIndexedMesh::AddPixmap(const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb) { + uint32_t vstart = vertices.size(); + vertices.resize(vertices.size() + 4); + + vertices[vstart + 0].pos = Vector3f::From(o); + vertices[vstart + 0].tex = Vector2f::From(ta.x, ta.y); + + vertices[vstart + 1].pos = Vector3f::From(o.Plus(v)); + vertices[vstart + 1].tex = Vector2f::From(ta.x, tb.y); + + vertices[vstart + 2].pos = Vector3f::From(o.Plus(u).Plus(v)); + vertices[vstart + 2].tex = Vector2f::From(tb.x, tb.y); + + vertices[vstart + 3].pos = Vector3f::From(o.Plus(u)); + vertices[vstart + 3].tex = Vector2f::From(tb.x, ta.y); + + size_t istart = indices.size(); + indices.resize(indices.size() + 6); + + indices[istart + 0] = vstart + 0; + indices[istart + 1] = vstart + 1; + indices[istart + 2] = vstart + 2; + indices[istart + 3] = vstart + 0; + indices[istart + 4] = vstart + 2; + indices[istart + 5] = vstart + 3; +} + +void SIndexedMesh::Clear() { + vertices.clear(); + indices.clear(); +} + +//----------------------------------------------------------------------------- +// Indexed mesh rendering +//----------------------------------------------------------------------------- + +void IndexedMeshRenderer::Init() { + colShader.Init( + "shaders/imesh.vert", "shaders/imesh.frag", + { + { ATTRIB_POS, "pos" } + } + ); + texShader.Init( + "shaders/imesh_tex.vert", "shaders/imesh_tex.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_TEX, "tex" } + } + ); + texaShader.Init( + "shaders/imesh_tex.vert", "shaders/imesh_texa.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_TEX, "tex" } + } + ); + pointShader.Init( + "shaders/imesh_point.vert", "shaders/imesh_point.frag", + { + { ATTRIB_POS, "pos" }, + { ATTRIB_TEX, "loc" } + } + ); + + texShader.SetUniformTextureUnit("texture_", 0); + texaShader.SetUniformTextureUnit("texture_", 0); + selectedShader = &colShader; +} + +void IndexedMeshRenderer::Clear() { + texShader.Clear(); + texaShader.Clear(); + colShader.Clear(); + pointShader.Clear(); +} + +IndexedMeshRenderer::Handle IndexedMeshRenderer::Add(const SIndexedMesh &m, bool dynamic) { + Handle handle; + glGenBuffers(1, &handle.vertexBuffer); + glGenBuffers(1, &handle.indexBuffer); + + GLenum mode = dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW; + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + glBufferData(GL_ARRAY_BUFFER, m.vertices.size() * sizeof(SIndexedMesh::Vertex), + m.vertices.data(), mode); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle.indexBuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, m.indices.size() * sizeof(uint32_t), + m.indices.data(), mode); + handle.size = m.indices.size(); + return handle; +} + +void IndexedMeshRenderer::Remove(const IndexedMeshRenderer::Handle &handle) { + glDeleteBuffers(1, &handle.vertexBuffer); + glDeleteBuffers(1, &handle.indexBuffer); +} + +void IndexedMeshRenderer::Draw(const IndexedMeshRenderer::Handle &handle) { + if(handle.size == 0) return; + + selectedShader->Enable(); + + glBindBuffer(GL_ARRAY_BUFFER, handle.vertexBuffer); + glEnableVertexAttribArray(ATTRIB_POS); + glVertexAttribPointer(ATTRIB_POS, 3, GL_FLOAT, GL_FALSE, sizeof(SIndexedMesh::Vertex), + (void *)offsetof(SIndexedMesh::Vertex, pos)); + if(NeedsTexture()) { + glEnableVertexAttribArray(ATTRIB_TEX); + glVertexAttribPointer(ATTRIB_TEX, 2, GL_FLOAT, GL_FALSE, sizeof(SIndexedMesh::Vertex), + (void *)offsetof(SIndexedMesh::Vertex, tex)); + } + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle.indexBuffer); + glDrawElements(GL_TRIANGLES, handle.size, GL_UNSIGNED_INT, NULL); + + glDisableVertexAttribArray(ATTRIB_POS); + if(NeedsTexture()) glDisableVertexAttribArray(ATTRIB_TEX); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + selectedShader->Disable(); +} + +void IndexedMeshRenderer::Draw(const SIndexedMesh &mesh) { + Handle handle = Add(mesh, /*dynamic=*/true) ; + Draw(handle); + Remove(handle); +} + +bool IndexedMeshRenderer::NeedsTexture() const { + return selectedShader == &texShader || + selectedShader == &texaShader || + selectedShader == &pointShader; +} + +void IndexedMeshRenderer::SetModelview(const double *matrix) { + colShader.SetUniformMatrix("modelview", matrix); + texShader.SetUniformMatrix("modelview", matrix); + texaShader.SetUniformMatrix("modelview", matrix); + pointShader.SetUniformMatrix("modelview", matrix); +} + +void IndexedMeshRenderer::SetProjection(const double *matrix) { + colShader.SetUniformMatrix("projection", matrix); + texShader.SetUniformMatrix("projection", matrix); + texaShader.SetUniformMatrix("projection", matrix); + pointShader.SetUniformMatrix("projection", matrix); +} + +void IndexedMeshRenderer::UseFilled(const Canvas::Fill &fill) { + if(fill.texture) { + selectedShader = (fill.texture->format == Pixmap::Format::A) ? &texaShader : &texShader; + } else { + selectedShader = &colShader; + } + selectedShader->SetUniformColor("color", fill.color); +} + +void IndexedMeshRenderer::UsePoint(const Canvas::Stroke &stroke, double pixel) { + pointShader.SetUniformColor("color", stroke.color); + pointShader.SetUniformFloat("width", (float)(stroke.width * pixel / 2.0)); + pointShader.SetUniformFloat("pixel", (float)pixel); + selectedShader = &pointShader; +} + +} diff --git a/src/render/gl3shader.h b/src/render/gl3shader.h new file mode 100644 index 0000000..1831e79 --- /dev/null +++ b/src/render/gl3shader.h @@ -0,0 +1,266 @@ +//----------------------------------------------------------------------------- +// OpenGL ES 2.0 and OpenGL 3.0 shader interface. +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +#ifndef SOLVESPACE_GL3SHADER_H +#define SOLVESPACE_GL3SHADER_H + +#if defined(WIN32) +# define GL_APICALL /*static linkage*/ +# define GL_GLEXT_PROTOTYPES +# include +# include +# define HAVE_GLES +#elif defined(__APPLE__) +# include +#else +# define GL_GLEXT_PROTOTYPES +# include +# include +#endif + +#if !defined(HAVE_GLES) +// glDepthRange is in GL1+ but not GLES2, glDepthRangef is in GL4.1+ and GLES2. +// Consistency! +# define glClearDepthf glClearDepth +# define glDepthRangef glDepthRange +#endif + +namespace SolveSpace { + +//----------------------------------------------------------------------------- +// Floating-point data structures; the layout of these must match shaders +//----------------------------------------------------------------------------- + +class Vector2f { +public: + float x, y; + + static Vector2f From(float x, float y); + static Vector2f From(double x, double y); + static Vector2f FromInt(uint32_t x, uint32_t y); +}; + +class Vector3f { +public: + float x, y, z; + + static Vector3f From(float x, float y, float z); + static Vector3f From(const Vector &v); + static Vector3f From(const RgbaColor &c); +}; + +class Vector4f { +public: + float x, y, z, w; + + static Vector4f From(float x, float y, float z, float w); + static Vector4f From(const Vector &v, float w); + static Vector4f FromInt(uint32_t x, uint32_t y, uint32_t z, uint32_t w); + static Vector4f From(const RgbaColor &c); +}; + +//----------------------------------------------------------------------------- +// Wrappers for our shaders +//----------------------------------------------------------------------------- + +class Shader { +public: + GLuint program = 0; + + void Init(const std::string &vertexRes, + const std::string &fragmentRes, + const std::vector> &locations = {}); + void Clear(); + + void SetUniformMatrix(const char *name, const double *md); + void SetUniformVector(const char *name, const Vector &v); + void SetUniformVector(const char *name, const Vector4f &v); + void SetUniformColor(const char *name, RgbaColor c); + void SetUniformFloat(const char *name, float v); + void SetUniformInt(const char *name, GLint v); + void SetUniformTextureUnit(const char *name, GLint index); + void Enable() const; + void Disable() const; +}; + +class MeshRenderer { +public: + const GLint ATTRIB_POS = 0; + const GLint ATTRIB_NOR = 1; + const GLint ATTRIB_COL = 2; + + struct MeshVertex { + Vector3f pos; + Vector3f nor; + Vector4f col; + }; + + struct Handle { + GLuint vertexBuffer; + GLsizei size; + }; + + Shader lightShader; + Shader fillShader; + Shader *selectedShader = NULL; + + void Init(); + void Clear(); + + Handle Add(const SMesh &m, bool dynamic = false); + void Remove(const Handle &handle); + void Draw(const Handle &handle, bool useColors = true, RgbaColor overrideColor = {}); + void Draw(const SMesh &mesh, bool useColors = true, RgbaColor overrideColor = {}); + + void SetModelview(double *matrix); + void SetProjection(double *matrix); + + void UseShaded(const Lighting &lighting); + void UseFilled(const Canvas::Fill &fill); +}; + +class StippleAtlas { +public: + std::vector patterns; + + void Init(); + void Clear(); + + GLint GetTexture(StipplePattern pattern) const; + double GetLength(StipplePattern pattern) const; +}; + +class EdgeRenderer { +public: + const GLint ATTRIB_POS = 0; + const GLint ATTRIB_LOC = 1; + const GLint ATTRIB_TAN = 2; + + struct EdgeVertex { + Vector3f pos; + Vector3f loc; + Vector3f tan; + }; + + struct Handle { + GLuint vertexBuffer; + GLuint indexBuffer; + GLsizei size; + }; + + Shader shader; + + const StippleAtlas *atlas = NULL; + StipplePattern pattern = StipplePattern::CONTINUOUS; + + void Init(const StippleAtlas *atlas); + void Clear(); + + Handle Add(const SEdgeList &edges, bool dynamic = false); + void Remove(const Handle &handle); + void Draw(const Handle &handle); + void Draw(const SEdgeList &edges); + + void SetModelview(const double *matrix); + void SetProjection(const double *matrix); + void SetStroke(const Canvas::Stroke &stroke, double pixel); +}; + +class OutlineRenderer { +public: + const GLint ATTRIB_POS = 0; + const GLint ATTRIB_LOC = 1; + const GLint ATTRIB_TAN = 2; + const GLint ATTRIB_NOL = 3; + const GLint ATTRIB_NOR = 4; + + struct OutlineVertex { + Vector3f pos; + Vector4f loc; + Vector3f tan; + Vector3f nol; + Vector3f nor; + }; + + struct Handle { + GLuint vertexBuffer; + GLuint indexBuffer; + GLsizei size; + }; + + Shader shader; + + const StippleAtlas *atlas = NULL; + StipplePattern pattern = StipplePattern::CONTINUOUS; + + void Init(const StippleAtlas *atlas); + void Clear(); + + Handle Add(const SOutlineList &outlines, bool dynamic = false); + void Remove(const Handle &handle); + void Draw(const Handle &handle, Canvas::DrawOutlinesAs mode); + void Draw(const SOutlineList &outlines, Canvas::DrawOutlinesAs mode); + + void SetModelview(const double *matrix); + void SetProjection(const double *matrix); + void SetStroke(const Canvas::Stroke &stroke, double pixel); +}; + +class SIndexedMesh { +public: + struct Vertex { + Vector3f pos; + Vector2f tex; + }; + + std::vector vertices; + std::vector indices; + + void AddPoint(const Vector &p); + void AddQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d); + void AddPixmap(const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb); + + void Clear(); +}; + +class IndexedMeshRenderer { +public: + const GLint ATTRIB_POS = 0; + const GLint ATTRIB_TEX = 1; + + struct Handle { + GLuint vertexBuffer; + GLuint indexBuffer; + GLsizei size; + }; + + Shader texShader; + Shader texaShader; + Shader colShader; + Shader pointShader; + + Shader *selectedShader = NULL; + + void Init(); + void Clear(); + + Handle Add(const SIndexedMesh &m, bool dynamic = false); + void Remove(const Handle &handle); + void Draw(const Handle &handle); + void Draw(const SIndexedMesh &mesh); + + void SetModelview(const double *matrix); + void SetProjection(const double *matrix); + + bool NeedsTexture() const; + + void UseFilled(const Canvas::Fill &fill); + void UsePoint(const Canvas::Stroke &stroke, double pixel); +}; + +} + +#endif diff --git a/src/render/render.cpp b/src/render/render.cpp new file mode 100644 index 0000000..89434c2 --- /dev/null +++ b/src/render/render.cpp @@ -0,0 +1,459 @@ +//----------------------------------------------------------------------------- +// Backend-agnostic rendering interface, and various backends we use. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" + +namespace SolveSpace { + +//----------------------------------------------------------------------------- +// Camera transformations. +//----------------------------------------------------------------------------- + +Point2d Camera::ProjectPoint(Vector p) const { + Vector p3 = ProjectPoint3(p); + Point2d p2 = { p3.x, p3.y }; + return p2; +} + +Vector Camera::ProjectPoint3(Vector p) const { + double w; + Vector r = ProjectPoint4(p, &w); + return r.ScaledBy(scale/w); +} + +Vector Camera::ProjectPoint4(Vector p, double *w) const { + p = p.Plus(offset); + + Vector r; + r.x = p.Dot(projRight); + r.y = p.Dot(projUp); + r.z = p.Dot(projUp.Cross(projRight)); + + *w = 1 + r.z*tangent*scale; + return r; +} + +Vector Camera::UnProjectPoint(Point2d p) const { + Vector orig = offset.ScaledBy(-1); + + // Note that we're ignoring the effects of perspective. Since our returned + // point has the same component normal to the screen as the offset, it + // will have z = 0 after the rotation is applied, thus w = 1. So this is + // correct. + orig = orig.Plus(projRight.ScaledBy(p.x / scale)).Plus( + projUp. ScaledBy(p.y / scale)); + return orig; +} + +Vector Camera::UnProjectPoint3(Vector p) const { + p.z = p.z / (scale - p.z * tangent * scale); + double w = 1 + p.z * tangent * scale; + p.x *= w / scale; + p.y *= w / scale; + + Vector orig = offset.ScaledBy(-1); + orig = orig.Plus(projRight.ScaledBy(p.x)).Plus( + projUp. ScaledBy(p.y).Plus( + projUp.Cross(projRight). ScaledBy(p.z))); + return orig; +} + +Vector Camera::VectorFromProjs(Vector rightUpForward) const { + Vector n = projRight.Cross(projUp); + + Vector r = (projRight.ScaledBy(rightUpForward.x)); + r = r.Plus(projUp.ScaledBy(rightUpForward.y)); + r = r.Plus(n.ScaledBy(rightUpForward.z)); + return r; +} + +Vector Camera::AlignToPixelGrid(Vector v) const { + if(!gridFit) return v; + + v = ProjectPoint3(v); + v.x = floor(v.x) + 0.5; + v.y = floor(v.y) + 0.5; + return UnProjectPoint3(v); +} + +SBezier Camera::ProjectBezier(SBezier b) const { + Quaternion q = Quaternion::From(projRight, projUp); + q = q.Inverse(); + // we want Q*(p - o) = Q*p - Q*o + b = b.TransformedBy(q.Rotate(offset).ScaledBy(scale), q, scale); + for(int i = 0; i <= b.deg; i++) { + Vector4 ct = Vector4::From(b.weight[i], b.ctrl[i]); + // so the desired curve, before perspective, is + // (x/w, y/w, z/w) + // and after perspective is + // ((x/w)/(1 - (z/w)*tangent, ... + // = (x/(w - z*tangent), ... + // so we want to let w' = w - z*tangent + ct.w = ct.w - ct.z*tangent; + + b.ctrl[i] = ct.PerspectiveProject(); + b.weight[i] = ct.w; + } + return b; +} + +void Camera::LoadIdentity() { + offset = { 0.0, 0.0, 0.0 }; + projRight = { 1.0, 0.0, 0.0 }; + projUp = { 0.0, 1.0, 0.0 }; + scale = 1.0; + tangent = 0.0; +} + +void Camera::NormalizeProjectionVectors() { + if(projRight.Magnitude() < LENGTH_EPS) { + projRight = Vector::From(1, 0, 0); + } + + Vector norm = projRight.Cross(projUp); + // If projRight and projUp somehow ended up parallel, then pick an + // arbitrary projUp normal to projRight. + if(norm.Magnitude() < LENGTH_EPS) { + norm = projRight.Normal(0); + } + projUp = norm.Cross(projRight); + + projUp = projUp.WithMagnitude(1); + projRight = projRight.WithMagnitude(1); +} + +//----------------------------------------------------------------------------- +// Stroke and fill caching. +//----------------------------------------------------------------------------- + +bool Canvas::Stroke::Equals(const Stroke &other) const { + return (layer == other.layer && + zIndex == other.zIndex && + color.Equals(other.color) && + width == other.width && + unit == other.unit && + stipplePattern == other.stipplePattern && + stippleScale == other.stippleScale); +} + +double Canvas::Stroke::WidthMm(const Camera &camera) const { + switch(unit) { + case Canvas::Unit::MM: + return width; + case Canvas::Unit::PX: + return width / camera.scale; + default: + ssassert(false, "Unexpected unit"); + } +} + +double Canvas::Stroke::WidthPx(const Camera &camera) const { + switch(unit) { + case Canvas::Unit::MM: + return width * camera.scale; + case Canvas::Unit::PX: + return width; + default: + ssassert(false, "Unexpected unit"); + } +} + +double Canvas::Stroke::StippleScaleMm(const Camera &camera) const { + switch(unit) { + case Canvas::Unit::MM: + return stippleScale; + case Canvas::Unit::PX: + return stippleScale / camera.scale; + default: + ssassert(false, "Unexpected unit"); + } +} + +double Canvas::Stroke::StippleScalePx(const Camera &camera) const { + switch(unit) { + case Canvas::Unit::MM: + return stippleScale * camera.scale; + case Canvas::Unit::PX: + return stippleScale; + default: + ssassert(false, "Unexpected unit"); + } +} + +bool Canvas::Fill::Equals(const Fill &other) const { + return (layer == other.layer && + zIndex == other.zIndex && + color.Equals(other.color) && + pattern == other.pattern && + texture == other.texture); +} + +void Canvas::Clear() { + strokes.Clear(); + fills.Clear(); +} + +Canvas::hStroke Canvas::GetStroke(const Stroke &stroke) { + for(const Stroke &s : strokes) { + if(s.Equals(stroke)) return s.h; + } + Stroke strokeCopy = stroke; + return strokes.AddAndAssignId(&strokeCopy); +} + +Canvas::hFill Canvas::GetFill(const Fill &fill) { + for(const Fill &f : fills) { + if(f.Equals(fill)) return f.h; + } + Fill fillCopy = fill; + return fills.AddAndAssignId(&fillCopy); +} + +BitmapFont *Canvas::GetBitmapFont() { + if(bitmapFont.IsEmpty()) { + bitmapFont = BitmapFont::Create(); + } + return &bitmapFont; +} + +std::shared_ptr Canvas::CreateBatch() { + return std::shared_ptr(); +} + +//----------------------------------------------------------------------------- +// An interface for view-independent visualization +//----------------------------------------------------------------------------- + +const Camera &BatchCanvas::GetCamera() const { + ssassert(false, "Geometry drawn on BatchCanvas must be independent from camera"); +} + +//----------------------------------------------------------------------------- +// A wrapper around Canvas that simplifies drawing UI in screen coordinates +//----------------------------------------------------------------------------- + +void UiCanvas::DrawLine(int x1, int y1, int x2, int y2, RgbaColor color, int width, int zIndex) { + Vector va = { (double)x1 + 0.5, (double)Flip(y1) + 0.5, 0.0 }, + vb = { (double)x2 + 0.5, (double)Flip(y2) + 0.5, 0.0 }; + + Canvas::Stroke stroke = {}; + stroke.layer = Canvas::Layer::NORMAL; + stroke.zIndex = zIndex; + stroke.width = (double)width; + stroke.color = color; + stroke.unit = Canvas::Unit::PX; + Canvas::hStroke hcs = canvas->GetStroke(stroke); + + canvas->DrawLine(va, vb, hcs); +} + +void UiCanvas::DrawRect(int l, int r, int t, int b, RgbaColor fillColor, RgbaColor outlineColor, + int zIndex) { + Vector va = { (double)l + 0.5, (double)Flip(b) + 0.5, 0.0 }, + vb = { (double)l + 0.5, (double)Flip(t) + 0.5, 0.0 }, + vc = { (double)r + 0.5, (double)Flip(t) + 0.5, 0.0 }, + vd = { (double)r + 0.5, (double)Flip(b) + 0.5, 0.0 }; + + if(!fillColor.IsEmpty()) { + Canvas::Fill fill = {}; + fill.layer = Canvas::Layer::NORMAL; + fill.zIndex = zIndex; + fill.color = fillColor; + Canvas::hFill hcf = canvas->GetFill(fill); + + canvas->DrawQuad(va, vb, vc, vd, hcf); + } + + if(!outlineColor.IsEmpty()) { + Canvas::Stroke stroke = {}; + stroke.layer = Canvas::Layer::NORMAL; + stroke.zIndex = zIndex; + stroke.width = 1.0; + stroke.color = outlineColor; + stroke.unit = Canvas::Unit::PX; + Canvas::hStroke hcs = canvas->GetStroke(stroke); + + canvas->DrawLine(va, vb, hcs); + canvas->DrawLine(vb, vc, hcs); + canvas->DrawLine(vc, vd, hcs); + canvas->DrawLine(vd, va, hcs); + } +} + +void UiCanvas::DrawPixmap(std::shared_ptr pm, int x, int y, int zIndex) { + Canvas::Fill fill = {}; + fill.layer = Canvas::Layer::NORMAL; + fill.zIndex = zIndex; + fill.color = { 255, 255, 255, 255 }; + Canvas::hFill hcf = canvas->GetFill(fill); + + canvas->DrawPixmap(pm, + { (double)x, (double)(flip ? Flip(y) - pm->height : y), 0.0 }, + { (double)pm->width, 0.0, 0.0 }, + { 0.0, (double)pm->height, 0.0 }, + { 0.0, 1.0 }, + { 1.0, 0.0 }, + hcf); +} + +void UiCanvas::DrawBitmapChar(char32_t codepoint, int x, int y, RgbaColor color, int zIndex) { + BitmapFont *font = canvas->GetBitmapFont(); + + Canvas::Fill fill = {}; + fill.layer = Canvas::Layer::NORMAL; + fill.zIndex = zIndex; + fill.color = color; + Canvas::hFill hcf = canvas->GetFill(fill); + + if(codepoint >= 0xe000 && codepoint <= 0xefff) { + // Special character, like a checkbox or a radio button + x -= 3; + } + + double s0, t0, s1, t1; + size_t w, h; + font->LocateGlyph(codepoint, &s0, &t0, &s1, &t1, &w, &h); + if(font->textureUpdated) { + // LocateGlyph modified the texture, reload it. + canvas->InvalidatePixmap(font->texture); + font->textureUpdated = false; + } + + canvas->DrawPixmap(font->texture, + { (double)x, (double)Flip(y), 0.0 }, + { (double)w, 0.0, 0.0 }, + { 0.0, (double) h, 0.0 }, + { s0, t1 }, + { s1, t0 }, + hcf); +} + +void UiCanvas::DrawBitmapText(const std::string &str, int x, int y, RgbaColor color, int zIndex) { + BitmapFont *font = canvas->GetBitmapFont(); + + for(char32_t codepoint : ReadUTF8(str)) { + DrawBitmapChar(codepoint, x, y, color, zIndex); + x += (int)font->GetWidth(codepoint) * 8; + } +} + +//----------------------------------------------------------------------------- +// A canvas that performs picking against drawn geometry. +//----------------------------------------------------------------------------- + +void ObjectPicker::DoCompare(double depth, double distance, int zIndex, int comparePosition) { + if(distance > selRadius) return; + if((zIndex == maxZIndex && distance < minDistance) || (zIndex > maxZIndex)) { + minDepth = depth; + minDistance = distance; + maxZIndex = zIndex; + position = comparePosition; + } +} + +void ObjectPicker::DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + int zIndex, int comparePosition) { + Point2d corners[4] = { + camera.ProjectPoint(a), + camera.ProjectPoint(b), + camera.ProjectPoint(c), + camera.ProjectPoint(d) + }; + double minNegative = VERY_NEGATIVE, + maxPositive = VERY_POSITIVE; + for(int i = 0; i < 4; i++) { + Point2d ap = corners[i], + bp = corners[(i + 1) % 4]; + double distance = point.DistanceToLineSigned(ap, bp.Minus(ap), /*asSegment=*/true); + if(distance < 0) minNegative = std::max(minNegative, distance); + if(distance > 0) maxPositive = std::min(maxPositive, distance); + } + + bool insideQuad = (minNegative == VERY_NEGATIVE || maxPositive == VERY_POSITIVE); + if(insideQuad) { + DoCompare(0, 0.0, zIndex, comparePosition); + } else { + double distance = std::min(fabs(minNegative), fabs(maxPositive)); + DoCompare(0, distance, zIndex, comparePosition); + } +} + +void ObjectPicker::DrawLine(const Vector &a, const Vector &b, hStroke hcs) { + Stroke *stroke = strokes.FindById(hcs); + Point2d ap = camera.ProjectPoint(a); + Point2d bp = camera.ProjectPoint(b); + double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true); + double depth = 0.5 * (camera.ProjectPoint3(a).z + camera.ProjectPoint3(b).z) ; + DoCompare(depth, distance - stroke->width / 2.0, stroke->zIndex); +} + +void ObjectPicker::DrawEdges(const SEdgeList &el, hStroke hcs) { + Stroke *stroke = strokes.FindById(hcs); + for(const SEdge &e : el.l) { + Point2d ap = camera.ProjectPoint(e.a); + Point2d bp = camera.ProjectPoint(e.b); + double distance = point.DistanceToLine(ap, bp.Minus(ap), /*asSegment=*/true); + double depth = 0.5 * (camera.ProjectPoint3(e.a).z + camera.ProjectPoint3(e.b).z) ; + DoCompare(depth, distance - stroke->width / 2.0, stroke->zIndex, e.auxB); + } +} + +void ObjectPicker::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) { + ssassert(false, "Not implemented"); +} + +void ObjectPicker::DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) { + Stroke *stroke = strokes.FindById(hcs); + double w = VectorFont::Builtin()-> GetWidth(height, text), + h = VectorFont::Builtin()->GetHeight(height); + DoQuad(o, + o.Plus(v.ScaledBy(h)), + o.Plus(u.ScaledBy(w)).Plus(v.ScaledBy(h)), + o.Plus(u.ScaledBy(w)), + stroke->zIndex); +} + +void ObjectPicker::DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) { + Fill *fill = fills.FindById(hcf); + DoQuad(a, b, c, d, fill->zIndex); +} + +void ObjectPicker::DrawPoint(const Vector &o, Canvas::hStroke hcs) { + Stroke *stroke = strokes.FindById(hcs); + double distance = point.DistanceTo(camera.ProjectPoint(o)) - stroke->width / 2; + double depth = camera.ProjectPoint3(o).z; + DoCompare(depth, distance, stroke->zIndex); +} + +void ObjectPicker::DrawPolygon(const SPolygon &p, hFill hcf) { + ssassert(false, "Not implemented"); +} + +void ObjectPicker::DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) { + ssassert(false, "Not implemented"); +} + +void ObjectPicker::DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) { + ssassert(false, "Not implemented"); +} + +void ObjectPicker::DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, Canvas::hFill hcf) { + DrawQuad(o, o.Plus(u), o.Plus(u).Plus(v), o.Plus(v), hcf); +} + +bool ObjectPicker::Pick(const std::function &drawFn) { + minDepth = VERY_POSITIVE; + minDistance = VERY_POSITIVE; + maxZIndex = INT_MIN; + + drawFn(); + return minDistance < selRadius; +} +} diff --git a/src/render/render.h b/src/render/render.h new file mode 100644 index 0000000..9601a5e --- /dev/null +++ b/src/render/render.h @@ -0,0 +1,385 @@ +//----------------------------------------------------------------------------- +// Backend-agnostic rendering interface, and various backends we use. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- + +#ifndef SOLVESPACE_RENDER_H +#define SOLVESPACE_RENDER_H + +//----------------------------------------------------------------------------- +// Interfaces common for all renderers +//----------------------------------------------------------------------------- + +enum class StipplePattern : uint32_t; + +// A mapping from 3d sketch coordinates to 2d screen coordinates, using +// an axonometric projection. +class Camera { +public: + double width; + double height; + double pixelRatio; + bool gridFit; + Vector offset; + Vector projRight; + Vector projUp; + double scale; + double tangent; + + bool IsPerspective() const { return tangent != 0.0; } + + Point2d ProjectPoint(Vector p) const; + Vector ProjectPoint3(Vector p) const; + Vector ProjectPoint4(Vector p, double *w) const; + Vector UnProjectPoint(Point2d p) const; + Vector UnProjectPoint3(Vector p) const; + Vector VectorFromProjs(Vector rightUpForward) const; + Vector AlignToPixelGrid(Vector v) const; + + SBezier ProjectBezier(SBezier b) const; + + void LoadIdentity(); + void NormalizeProjectionVectors(); +}; + +// A description of scene lighting. +class Lighting { +public: + RgbaColor backgroundColor; + double ambientIntensity; + double lightIntensity[2]; + Vector lightDirection[2]; +}; + +class BatchCanvas; + +// An interface for populating a drawing area with geometry. +class Canvas { +public: + // Stroke and fill styles are addressed with handles to be able to quickly + // group geometry into indexed draw calls. + class hStroke { + public: + uint32_t v; + }; + + class hFill { + public: + uint32_t v; + }; + + // The layer of a geometry describes how it occludes other geometry. + // Within a layer, geometry with higher z-index occludes geometry with lower z-index, + // or geometry drawn earlier if z-indexes match. + enum class Layer { + NORMAL, // Occluded by geometry with lower Z coordinate + OCCLUDED, // Only drawn over geometry with lower Z coordinate + DEPTH_ONLY, // Like NORMAL, but only affects future occlusion, not color + BACK, // Always drawn below all other geometry + FRONT, // Always drawn above all other geometry + LAST = FRONT + }; + + // The outlines are the collection of all edges that may be drawn. + // Outlines can be classified as emphasized or not; emphasized outlines indicate an abrupt + // change in the surface curvature. These are indicated by the SOutline tag. + // Outlines can also be classified as contour or not; contour outlines indicate the boundary + // of the filled mesh. Whether an outline is a part of contour or not depends on point of view. + enum class DrawOutlinesAs { + EMPHASIZED_AND_CONTOUR = 0, // Both emphasized and contour outlines + EMPHASIZED_WITHOUT_CONTOUR = 1, // Emphasized outlines except those also belonging to contour + CONTOUR_ONLY = 2 // Contour outlines only + }; + + // Stroke widths, etc, can be scale-invariant (in pixels) or scale-dependent (in millimeters). + enum class Unit { + MM, + PX + }; + + class Stroke { + public: + hStroke h; + + Layer layer; + int zIndex; + RgbaColor color; + double width; + Unit unit; + StipplePattern stipplePattern; + double stippleScale; + + void Clear() { *this = {}; } + bool Equals(const Stroke &other) const; + + double WidthMm(const Camera &camera) const; + double WidthPx(const Camera &camera) const; + double StippleScaleMm(const Camera &camera) const; + double StippleScalePx(const Camera &camera) const; + }; + + enum class FillPattern { + SOLID, CHECKERED_A, CHECKERED_B + }; + + class Fill { + public: + hFill h; + + Layer layer; + int zIndex; + RgbaColor color; + FillPattern pattern; + std::shared_ptr texture; + + void Clear() { *this = {}; } + bool Equals(const Fill &other) const; + }; + + IdList strokes = {}; + IdList fills = {}; + BitmapFont bitmapFont = {}; + + virtual void Clear(); + virtual ~Canvas() = default; + + hStroke GetStroke(const Stroke &stroke); + hFill GetFill(const Fill &fill); + BitmapFont *GetBitmapFont(); + + virtual const Camera &GetCamera() const = 0; + + virtual void DrawLine(const Vector &a, const Vector &b, hStroke hcs) = 0; + virtual void DrawEdges(const SEdgeList &el, hStroke hcs) = 0; + virtual bool DrawBeziers(const SBezierList &bl, hStroke hcs) = 0; + virtual void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) = 0; + virtual void DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) = 0; + + virtual void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) = 0; + virtual void DrawPoint(const Vector &o, hStroke hcs) = 0; + virtual void DrawPolygon(const SPolygon &p, hFill hcf) = 0; + virtual void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {}) = 0; + virtual void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) = 0; + + virtual void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) = 0; + virtual void InvalidatePixmap(std::shared_ptr pm) = 0; + + virtual std::shared_ptr CreateBatch(); +}; + +template<> +struct IsHandleOracle : std::true_type {}; + +template<> +struct IsHandleOracle : std::true_type {}; + + +// An interface for view-dependent visualization. +class ViewportCanvas : public Canvas { +public: + virtual void SetCamera(const Camera &camera) = 0; + virtual void SetLighting(const Lighting &lighting) = 0; + + virtual void StartFrame() = 0; + virtual void FlushFrame() = 0; + virtual void FinishFrame() = 0; + virtual std::shared_ptr ReadFrame() = 0; + + virtual void GetIdent(const char **vendor, const char **renderer, const char **version) = 0; +}; + +// An interface for view-independent visualization. +class BatchCanvas : public Canvas { +public: + const Camera &GetCamera() const override; + + virtual void Finalize() = 0; + virtual void Draw() = 0; +}; + +// A wrapper around Canvas that simplifies drawing UI in screen coordinates. +class UiCanvas { +public: + std::shared_ptr canvas; + bool flip = false; + + void DrawLine(int x1, int y1, int x2, int y2, RgbaColor color, int width = 1, + int zIndex = 0); + void DrawRect(int l, int r, int t, int b, RgbaColor fillColor, RgbaColor outlineColor, + int zIndex = 0); + void DrawPixmap(std::shared_ptr pm, int x, int y, + int zIndex = 0); + void DrawBitmapChar(char32_t codepoint, int x, int y, RgbaColor color, + int zIndex = 0); + void DrawBitmapText(const std::string &str, int x, int y, RgbaColor color, + int zIndex = 0); + + int Flip(int y) const { return flip ? (int)canvas->GetCamera().height - y : y; } +}; + +// A canvas that performs picking against drawn geometry. +class ObjectPicker : public Canvas { +public: + Camera camera = {}; + // Configuration. + Point2d point = {}; + double selRadius = 0.0; + // Picking state. + double minDistance = 0.0; + double minDepth = 1e10; + int maxZIndex = 0; + uint32_t position = 0; + + const Camera &GetCamera() const override { return camera; } + + void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; + void DrawEdges(const SEdgeList &el, hStroke hcs) override; + bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; } + void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override; + void DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) override; + + void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) override; + void DrawPoint(const Vector &o, hStroke hcs) override; + void DrawPolygon(const SPolygon &p, hFill hcf) override; + void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) override; + void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override; + + void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) override; + void InvalidatePixmap(std::shared_ptr pm) override {} + + void DoCompare(double depth, double distance, int zIndex, int comparePosition = 0); + void DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + int zIndex, int comparePosition = 0); + + bool Pick(const std::function &drawFn); +}; + +// A canvas that renders onto a 2d surface, performing z-index sorting, occlusion testing, etc, +// on the CPU. +class SurfaceRenderer : public ViewportCanvas { +public: + Camera camera = {}; + Lighting lighting = {}; + // Chord tolerance, for converting beziers to pwl. + double chordTolerance = 0.0; + // Render lists. + handle_map edges; + handle_map beziers; + SMesh mesh = {}; + // State. + BBox bbox = {}; + + void Clear() override; + + // Canvas interface. + const Camera &GetCamera() const override { return camera; } + + // ViewportCanvas interface. + void SetCamera(const Camera &cam) override { this->camera = cam; } + void SetLighting(const Lighting &light) override { this->lighting = light; } + + void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; + void DrawEdges(const SEdgeList &el, hStroke hcs) override; + bool DrawBeziers(const SBezierList &bl, hStroke hcs) override; + void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override; + void DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) override; + + void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) override; + void DrawPoint(const Vector &o, hStroke hcs) override; + void DrawPolygon(const SPolygon &p, hFill hcf) override; + void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) override; + void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override; + + void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) override; + void InvalidatePixmap(std::shared_ptr pm) override; + + // Geometry manipulation. + void CalculateBBox(); + void ConvertBeziersToEdges(); + void CullOccludedStrokes(); + + // Renderer operations. + void OutputInPaintOrder(); + + virtual bool CanOutputCurves() const = 0; + virtual bool CanOutputTriangles() const = 0; + + virtual void OutputStart() = 0; + virtual void OutputBezier(const SBezier &b, hStroke hcs) = 0; + virtual void OutputTriangle(const STriangle &tr) = 0; + virtual void OutputEnd() = 0; + + void OutputBezierAsNonrationalCubic(const SBezier &b, hStroke hcs); +}; + +//----------------------------------------------------------------------------- +// 2d renderers +//----------------------------------------------------------------------------- + +class CairoRenderer : public SurfaceRenderer { +public: + cairo_t *context = NULL; + // Renderer configuration. + bool antialias = false; + // Renderer state. + struct { + hStroke hcs; + } current = {}; + + void Clear() override; + + void StartFrame() override {} + void FlushFrame() override; + void FinishFrame() override {} + std::shared_ptr ReadFrame() override; + + void GetIdent(const char **vendor, const char **renderer, const char **version) override; + + void SelectStroke(hStroke hcs); + void MoveTo(Vector p); + void FinishPath(); + + bool CanOutputCurves() const override { return true; } + bool CanOutputTriangles() const override { return true; } + + void OutputStart() override; + void OutputBezier(const SBezier &b, hStroke hcs) override; + void OutputTriangle(const STriangle &tr) override; + void OutputEnd() override; +}; + +class CairoPixmapRenderer final : public CairoRenderer { +public: + std::shared_ptr pixmap; + + cairo_surface_t *surface = NULL; + + void Init(); + void Clear() override; + + std::shared_ptr ReadFrame() override; +}; + +//----------------------------------------------------------------------------- +// Factories +//----------------------------------------------------------------------------- + +std::shared_ptr CreateRenderer(); + +#endif diff --git a/src/render/render2d.cpp b/src/render/render2d.cpp new file mode 100644 index 0000000..710e795 --- /dev/null +++ b/src/render/render2d.cpp @@ -0,0 +1,413 @@ +//----------------------------------------------------------------------------- +// Rendering projections to 2d surfaces: z-sorting, occlusion testing, etc. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" + +namespace SolveSpace { + +// FIXME: The export coordinate system has a different handedness than display +// coordinate system; lighting and occlusion calculations are right-handed. +static Vector ProjectPoint3RH(const Camera &camera, Vector p) { + p = p.Plus(camera.offset); + + Vector r; + r.x = p.Dot(camera.projRight); + r.y = p.Dot(camera.projUp); + r.z = p.Dot(camera.projRight.Cross(camera.projUp)); + + double w = 1 + r.z*camera.tangent*camera.scale; + return r.ScaledBy(camera.scale/w); +} + +//----------------------------------------------------------------------------- +// Accumulation of geometry +//----------------------------------------------------------------------------- + +void SurfaceRenderer::DrawLine(const Vector &a, const Vector &b, hStroke hcs) { + edges[hcs].AddEdge(ProjectPoint3RH(camera, a), + ProjectPoint3RH(camera, b)); +} + +void SurfaceRenderer::DrawEdges(const SEdgeList &el, hStroke hcs) { + for(const SEdge &e : el.l) { + edges[hcs].AddEdge(ProjectPoint3RH(camera, e.a), + ProjectPoint3RH(camera, e.b)); + } +} + +bool SurfaceRenderer::DrawBeziers(const SBezierList &bl, hStroke hcs) { + if(!CanOutputCurves()) + return false; + + for(const SBezier &b : bl.l) { + SBezier pb = camera.ProjectBezier(b); + beziers[hcs].l.Add(&pb); + } + return true; +} + +void SurfaceRenderer::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) { + Vector projDir = camera.projRight.Cross(camera.projUp); + for(const SOutline &o : ol.l) { + if(drawAs == DrawOutlinesAs::EMPHASIZED_AND_CONTOUR && + !(o.IsVisible(projDir) || o.tag != 0)) + continue; + if(drawAs == DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR && + !(!o.IsVisible(projDir) && o.tag != 0)) + continue; + if(drawAs == DrawOutlinesAs::CONTOUR_ONLY && + !(o.IsVisible(projDir))) + continue; + + edges[hcs].AddEdge(ProjectPoint3RH(camera, o.a), + ProjectPoint3RH(camera, o.b)); + } +} + +void SurfaceRenderer::DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) { + auto traceEdge = [&](Vector a, Vector b) { + edges[hcs].AddEdge(ProjectPoint3RH(camera, a), + ProjectPoint3RH(camera, b)); + }; + VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera); +} + +void SurfaceRenderer::DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) { + Fill *fill = fills.FindById(hcf); + ssassert(fill->layer == Layer::NORMAL || + fill->layer == Layer::DEPTH_ONLY || + fill->layer == Layer::FRONT || + fill->layer == Layer::BACK, "Unexpected mesh layer"); + + Vector zOffset = {}; + if(fill->layer == Layer::BACK) { + zOffset.z -= 1e6; + } else if(fill->layer == Layer::FRONT) { + zOffset.z += 1e6; + } + zOffset.z += camera.scale * fill->zIndex; + + STriMeta meta = {}; + if(fill->layer != Layer::DEPTH_ONLY) { + meta.color = fill->color; + } + Vector ta = ProjectPoint3RH(camera, a).Plus(zOffset), + tb = ProjectPoint3RH(camera, b).Plus(zOffset), + tc = ProjectPoint3RH(camera, c).Plus(zOffset), + td = ProjectPoint3RH(camera, d).Plus(zOffset); + mesh.AddTriangle(meta, tc, tb, ta); + mesh.AddTriangle(meta, ta, td, tc); +} + +void SurfaceRenderer::DrawPoint(const Vector &o, Canvas::hStroke hcs) { + Stroke *stroke = strokes.FindById(hcs); + + Fill fill = {}; + fill.layer = stroke->layer; + fill.zIndex = stroke->zIndex; + fill.color = stroke->color; + hFill hcf = GetFill(fill); + + Vector u = camera.projRight.ScaledBy(stroke->width/2.0/camera.scale), + v = camera.projUp.ScaledBy(stroke->width/2.0/camera.scale); + DrawQuad(o.Minus(u).Minus(v), o.Minus(u).Plus(v), + o.Plus(u).Plus(v), o.Plus(u).Minus(v), hcf); +} + +void SurfaceRenderer::DrawPolygon(const SPolygon &p, hFill hcf) { + SMesh m = {}; + p.TriangulateInto(&m); + DrawMesh(m, hcf, {}); + m.Clear(); +} + +void SurfaceRenderer::DrawMesh(const SMesh &m, + hFill hcfFront, hFill hcfBack) { + Fill *fill = fills.FindById(hcfFront); + ssassert(fill->layer == Layer::NORMAL || + fill->layer == Layer::DEPTH_ONLY, "Unexpected mesh layer"); + + Vector l0 = (lighting.lightDirection[0]).WithMagnitude(1), + l1 = (lighting.lightDirection[1]).WithMagnitude(1); + for(STriangle tr : m.l) { + tr.a = ProjectPoint3RH(camera, tr.a); + tr.b = ProjectPoint3RH(camera, tr.b); + tr.c = ProjectPoint3RH(camera, tr.c); + + if(CanOutputTriangles() && fill->layer == Layer::NORMAL) { + if(fill->color.IsEmpty()) { + // Compute lighting, since we're going to draw the shaded triangles. + Vector n = tr.Normal().WithMagnitude(1); + double intensity = lighting.ambientIntensity + + max(0.0, (lighting.lightIntensity[0])*(n.Dot(l0))) + + max(0.0, (lighting.lightIntensity[1])*(n.Dot(l1))); + double r = min(1.0, tr.meta.color.redF() * intensity), + g = min(1.0, tr.meta.color.greenF() * intensity), + b = min(1.0, tr.meta.color.blueF() * intensity); + tr.meta.color = RGBf(r, g, b); + } else { + // We're going to draw this triangle, but it's not shaded. + tr.meta.color = fill->color; + } + } else { + // This triangle is just for occlusion testing. + tr.meta.color = {}; + } + mesh.AddTriangle(&tr); + } +} + +void SurfaceRenderer::DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) { + Fill *fill = fills.FindById(hcf); + ssassert(fill->layer == Layer::NORMAL || + fill->layer == Layer::DEPTH_ONLY, "Unexpected mesh layer"); + + Vector zOffset = {}; + zOffset.z += camera.scale * fill->zIndex; + + size_t facesSize = faces.size(); + for(STriangle tr : m.l) { + uint32_t face = tr.meta.face; + for(size_t j = 0; j < facesSize; j++) { + if(faces[j] != face) continue; + if(!fill->color.IsEmpty()) { + tr.meta.color = fill->color; + } + mesh.AddTriangle(tr.meta, + ProjectPoint3RH(camera, tr.a).Plus(zOffset), + ProjectPoint3RH(camera, tr.b).Plus(zOffset), + ProjectPoint3RH(camera, tr.c).Plus(zOffset)); + break; + } + } +} + +void SurfaceRenderer::DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) { + ssassert(false, "Not implemented"); +} + +void SurfaceRenderer::InvalidatePixmap(std::shared_ptr pm) { + ssassert(false, "Not implemented"); +} + +//----------------------------------------------------------------------------- +// Processing of geometry +//----------------------------------------------------------------------------- + +void SurfaceRenderer::CalculateBBox() { + bbox.minp = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE); + bbox.maxp = Vector::From(VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE); + + for(auto &it : edges) { + SEdgeList &el = it.second; + for(SEdge &e : el.l) { + bbox.Include(e.a); + bbox.Include(e.b); + } + } + + for(auto &it : beziers) { + SBezierList &bl = it.second; + for(SBezier &b : bl.l) { + for(int i = 0; i <= b.deg; i++) { + bbox.Include(b.ctrl[i]); + } + } + } + + for(STriangle &tr : mesh.l) { + for(int i = 0; i < 3; i++) { + bbox.Include(tr.vertices[i]); + } + } +} + + +void SurfaceRenderer::ConvertBeziersToEdges() { + for(auto &it : beziers) { + hStroke hcs = it.first; + SBezierList &bl = it.second; + + SEdgeList &el = edges[hcs]; + for(const SBezier &b : bl.l) { + if(b.deg == 1) { + el.AddEdge(b.ctrl[0], b.ctrl[1]); + } else { + List lv = {}; + b.MakePwlInto(&lv, chordTolerance); + for(int i = 1; i < lv.n; i++) { + el.AddEdge(lv[i-1], lv[i]); + } + lv.Clear(); + } + } + bl.l.Clear(); + } + beziers.clear(); +} + +void SurfaceRenderer::CullOccludedStrokes() { + // Perform occlusion testing, if necessary. + if(mesh.l.IsEmpty()) + return; + + // We can't perform hidden line removal on exact curves. + ConvertBeziersToEdges(); + + // Remove hidden lines (on NORMAL layers), or remove visible lines (on OCCLUDED layers). + SKdNode *root = SKdNode::From(&mesh); + root->ClearTags(); + + int cnt = 1234; + for(auto &eit : edges) { + hStroke hcs = eit.first; + SEdgeList &el = eit.second; + + Stroke *stroke = strokes.FindById(hcs); + if(stroke->layer != Layer::NORMAL && + stroke->layer != Layer::OCCLUDED) continue; + + SEdgeList nel = {}; + for(const SEdge &e : el.l) { + SEdgeList oel = {}; + oel.AddEdge(e.a, e.b); + root->OcclusionTestLine(e, &oel, cnt); + + if(stroke->layer == Layer::OCCLUDED) { + for(SEdge &oe : oel.l) { + oe.tag = !oe.tag; + } + } + oel.l.RemoveTagged(); + + oel.MergeCollinearSegments(e.a, e.b); + for(const SEdge &oe : oel.l) { + nel.AddEdge(oe.a, oe.b); + } + + oel.Clear(); + cnt++; + } + + el.l.Clear(); + el.l = nel.l; + } +} + +void SurfaceRenderer::OutputInPaintOrder() { + // Sort our strokes in paint order. + std::vector> paintOrder; + paintOrder.emplace_back(Layer::NORMAL, 0); // mesh + for(const Stroke &cs : strokes) { + paintOrder.emplace_back(cs.layer, cs.zIndex); + } + + const Layer stackup[] = { + Layer::BACK, Layer::NORMAL, Layer::DEPTH_ONLY, Layer::OCCLUDED, Layer::FRONT + }; + std::sort(paintOrder.begin(), paintOrder.end(), + [&](std::pair a, std::pair b) { + Layer aLayer = a.first, + bLayer = b.first; + int aZIndex = a.second, + bZIndex = b.second; + + size_t aLayerIndex = + std::find(std::begin(stackup), std::end(stackup), aLayer) - std::begin(stackup); + size_t bLayerIndex = + std::find(std::begin(stackup), std::end(stackup), bLayer) - std::begin(stackup); + if(aLayerIndex == bLayerIndex) { + return aZIndex < bZIndex; + } else { + return aLayerIndex < bLayerIndex; + } + }); + + auto last = std::unique(paintOrder.begin(), paintOrder.end()); + paintOrder.erase(last, paintOrder.end()); + + // Output geometry in paint order. + OutputStart(); + for(auto &it : paintOrder) { + Layer layer = it.first; + int zIndex = it.second; + + if(layer == Layer::NORMAL && zIndex == 0) { + SMesh mp = {}; + SBsp3 *bsp = SBsp3::FromMesh(&mesh); + if(bsp) bsp->GenerateInPaintOrder(&mp); + + for(const STriangle &tr : mp.l) { + // Cull back-facing and invisible triangles. + if(tr.Normal().z < 0) continue; + if(tr.meta.color.IsEmpty()) continue; + OutputTriangle(tr); + } + + mp.Clear(); + } + + for(auto eit : edges) { + hStroke hcs = eit.first; + const SEdgeList &el = eit.second; + + Stroke *stroke = strokes.FindById(hcs); + if(stroke->layer != layer || stroke->zIndex != zIndex) continue; + + for(const SEdge &e : el.l) { + OutputBezier(SBezier::From(e.a, e.b), hcs); + } + } + + for(auto &bit : beziers) { + hStroke hcs = bit.first; + const SBezierList &bl = bit.second; + + Stroke *stroke = strokes.FindById(hcs); + if(stroke->layer != layer || stroke->zIndex != zIndex) continue; + + for(const SBezier &b : bl.l) { + OutputBezier(b, hcs); + } + } + } + OutputEnd(); +} + +void SurfaceRenderer::Clear() { + Canvas::Clear(); + + for(auto &eit : edges) { + SEdgeList &el = eit.second; + el.l.Clear(); + } + edges.clear(); + + for(auto &bit : beziers) { + SBezierList &bl = bit.second; + bl.l.Clear(); + } + beziers.clear(); + + mesh.Clear(); +} + +void SurfaceRenderer::OutputBezierAsNonrationalCubic(const SBezier &b, hStroke hcs) { + // Arbitrary choice of tolerance; make it a little finer than pwl tolerance since + // it should be easier to achieve that with the smooth curves. + SBezierList bl; + b.MakeNonrationalCubicInto(&bl, chordTolerance / 2); + for(const SBezier &cb : bl.l) { + OutputBezier(cb, hcs); + } + bl.Clear(); +} + +} diff --git a/src/render/rendercairo.cpp b/src/render/rendercairo.cpp new file mode 100644 index 0000000..e59de31 --- /dev/null +++ b/src/render/rendercairo.cpp @@ -0,0 +1,173 @@ +//----------------------------------------------------------------------------- +// A rendering backend that draws on a Cairo surface. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include +#include "solvespace.h" + +namespace SolveSpace { + +void CairoRenderer::Clear() { + SurfaceRenderer::Clear(); + + if(context != NULL) cairo_destroy(context); + context = NULL; +} + +void CairoRenderer::GetIdent(const char **vendor, const char **renderer, const char **version) { + *vendor = "Cairo"; + *renderer = "Cairo"; + *version = cairo_version_string(); +} + +void CairoRenderer::FlushFrame() { + CullOccludedStrokes(); + OutputInPaintOrder(); + + cairo_surface_flush(cairo_get_target(context)); +} + +std::shared_ptr CairoRenderer::ReadFrame() { + ssassert(false, "generic Cairo renderer does not support pixmap readout"); +} + +void CairoRenderer::OutputStart() { + cairo_save(context); + + RgbaColor bgColor = lighting.backgroundColor; + cairo_rectangle(context, 0.0, 0.0, (double)camera.width, (double)camera.height); + cairo_set_source_rgba(context, bgColor.redF(), bgColor.greenF(), bgColor.blueF(), + bgColor.alphaF()); + cairo_fill(context); + + cairo_translate(context, camera.width / 2.0, camera.height / 2.0); + + // Avoid pixel boundaries; when not using antialiasing, we would otherwise + // get numerically unstable output. + cairo_translate(context, 0.1, 0.1); + + cairo_set_line_join(context, CAIRO_LINE_JOIN_ROUND); + cairo_set_line_cap(context, CAIRO_LINE_CAP_ROUND); +} + +void CairoRenderer::OutputEnd() { + FinishPath(); + + cairo_restore(context); +} + +void CairoRenderer::SelectStroke(hStroke hcs) { + if(current.hcs == hcs) return; + FinishPath(); + + Stroke *stroke = strokes.FindById(hcs); + current.hcs = hcs; + + RgbaColor color = stroke->color; + std::vector dashes = StipplePatternDashes(stroke->stipplePattern); + for(double &dash : dashes) { + dash *= stroke->StippleScalePx(camera); + } + cairo_set_line_width(context, stroke->WidthPx(camera)); + cairo_set_dash(context, dashes.data(), (int)dashes.size(), 0); + cairo_set_source_rgba(context, color.redF(), color.greenF(), color.blueF(), + color.alphaF()); + if(antialias) { + cairo_set_antialias(context, CAIRO_ANTIALIAS_GRAY); + } else { + cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE); + } +} + +void CairoRenderer::MoveTo(Vector p) { + Point2d pos; + cairo_get_current_point(context, &pos.x, &pos.y); + if(cairo_has_current_point(context) && pos.Equals(p.ProjectXy())) return; + FinishPath(); + + cairo_move_to(context, p.x, p.y); +} + +void CairoRenderer::FinishPath() { + if(!cairo_has_current_point(context)) return; + + cairo_stroke(context); +} + +void CairoRenderer::OutputBezier(const SBezier &b, hStroke hcs) { + SelectStroke(hcs); + + Vector c, n = Vector::From(0, 0, 1); + double r; + if(b.deg == 1) { + MoveTo(b.ctrl[0]); + cairo_line_to(context, + b.ctrl[1].x, b.ctrl[1].y); + } else if(b.IsCircle(n, &c, &r)) { + MoveTo(b.ctrl[0]); + double theta0 = atan2(b.ctrl[0].y - c.y, b.ctrl[0].x - c.x), + theta1 = atan2(b.ctrl[2].y - c.y, b.ctrl[2].x - c.x), + dtheta = WRAP_SYMMETRIC(theta1 - theta0, 2*PI); + if(dtheta > 0) { + cairo_arc(context, + c.x, c.y, r, theta0, theta1); + } else { + cairo_arc_negative(context, + c.x, c.y, r, theta0, theta1); + } + } else if(b.deg == 3 && !b.IsRational()) { + MoveTo(b.ctrl[0]); + cairo_curve_to(context, + b.ctrl[1].x, b.ctrl[1].y, + b.ctrl[2].x, b.ctrl[2].y, + b.ctrl[3].x, b.ctrl[3].y); + } else { + OutputBezierAsNonrationalCubic(b, hcs); + } +} + +void CairoRenderer::OutputTriangle(const STriangle &tr) { + FinishPath(); + current.hcs = {}; + + RgbaColor color = tr.meta.color; + cairo_set_source_rgba(context, color.redF(), color.greenF(), color.blueF(), + color.alphaF()); + cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE); + cairo_move_to(context, tr.a.x, tr.a.y); + cairo_line_to(context, tr.b.x, tr.b.y); + cairo_line_to(context, tr.c.x, tr.c.y); + cairo_fill(context); +} + +void CairoPixmapRenderer::Init() { + Clear(); + + pixmap = std::make_shared(); + pixmap->format = Pixmap::Format::BGRA; + pixmap->width = (size_t)camera.width; + pixmap->height = (size_t)camera.height; + pixmap->stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, (int)camera.width); + pixmap->data = std::vector(pixmap->stride * pixmap->height); + surface = + cairo_image_surface_create_for_data(&pixmap->data[0], CAIRO_FORMAT_RGB24, + pixmap->width, pixmap->height, + pixmap->stride); + context = cairo_create(surface); +} + +void CairoPixmapRenderer::Clear() { + CairoRenderer::Clear(); + + if(surface != NULL) cairo_surface_destroy(surface); + surface = NULL; +} + +std::shared_ptr CairoPixmapRenderer::ReadFrame() { + std::shared_ptr result = pixmap->Copy(); + result->ConvertTo(Pixmap::Format::RGBA); + return result; +} + +} diff --git a/src/render/rendergl1.cpp b/src/render/rendergl1.cpp new file mode 100644 index 0000000..ef34bc5 --- /dev/null +++ b/src/render/rendergl1.cpp @@ -0,0 +1,852 @@ +//----------------------------------------------------------------------------- +// OpenGL 1 based rendering interface. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" + +#ifdef WIN32 +// Include after solvespace.h to avoid identifier clashes. +# include // required by GL headers +#endif +#ifdef __APPLE__ +# include +# include +#else +# include +# include +#endif + +namespace SolveSpace { + +//----------------------------------------------------------------------------- +// Checks for buggy OpenGL renderers +//----------------------------------------------------------------------------- + +// Intel GPUs with Mesa on *nix render thin lines poorly. +static bool HasIntelThinLineQuirk() +{ + static bool quirkChecked, quirkEnabled; + if(!quirkChecked) { + const char *ident = (const char*)glGetString(GL_VENDOR); + if(ident != NULL) { + quirkChecked = true; + quirkEnabled = !strcmp(ident, "Intel Open Source Technology Center"); + } + } + return quirkEnabled; +} + +// The default Windows GL renderer really does implement GL 1.1, +// and cannot handle non-power-of-2 textures, which is legal. +static bool HasGl1V1Quirk() +{ + static bool quirkChecked, quirkEnabled; + if(!quirkChecked) { + const char *ident = (const char*)glGetString(GL_VERSION); + if(ident != NULL) { + quirkChecked = true; + quirkEnabled = !strcmp(ident, "1.1.0"); + } + } + return quirkEnabled; +} + +//----------------------------------------------------------------------------- +// Thin wrappers around OpenGL functions to fix bugs, adapt them to our +// data structures, etc. +//----------------------------------------------------------------------------- + +static inline void ssglNormal3v(Vector n) { + glNormal3d(n.x, n.y, n.z); +} + +static inline void ssglVertex3v(Vector v) { + glVertex3d(v.x, v.y, v.z); +} + +void ssglLineWidth(double width) { + if(HasIntelThinLineQuirk() && width < 1.6) + width = 1.6; + + glLineWidth((GLfloat)width); +} + +static inline void ssglColorRGBA(RgbaColor color) { + glColor4d(color.redF(), color.greenF(), color.blueF(), color.alphaF()); +} + +static inline void ssglMaterialRGBA(GLenum side, RgbaColor color) { + GLfloat mpb[] = { color.redF(), color.greenF(), color.blueF(), color.alphaF() }; + glMaterialfv(side, GL_AMBIENT_AND_DIFFUSE, mpb); +} + +static void ssglDepthRange(Canvas::Layer layer, int zIndex) { + switch(layer) { + case Canvas::Layer::NORMAL: + case Canvas::Layer::FRONT: + case Canvas::Layer::BACK: + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + break; + + case Canvas::Layer::DEPTH_ONLY: + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + break; + + case Canvas::Layer::OCCLUDED: + glDepthFunc(GL_GREATER); + glDepthMask(GL_FALSE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + break; + } + + switch(layer) { + case Canvas::Layer::FRONT: + glDepthRange(0.0, 0.0); + break; + + case Canvas::Layer::BACK: + glDepthRange(1.0, 1.0); + break; + + case Canvas::Layer::NORMAL: + case Canvas::Layer::DEPTH_ONLY: + case Canvas::Layer::OCCLUDED: + // The size of this step depends on the resolution of the Z buffer; for + // a 16-bit buffer, this should be fine. + double offset = 1.0 / (65535 * 0.8) * zIndex; + glDepthRange(0.1 - offset, 1.0 - offset); + break; + } +} + +static void ssglFillPattern(Canvas::FillPattern pattern) { + static bool Init; + static GLubyte MaskA[(32*32)/8]; + static GLubyte MaskB[(32*32)/8]; + if(!Init) { + int x, y; + for(x = 0; x < 32; x++) { + for(y = 0; y < 32; y++) { + int i = y*4 + x/8, b = x % 8; + int ym = y % 4, xm = x % 4; + for(int k = 0; k < 2; k++) { + if(xm >= 1 && xm <= 2 && ym >= 1 && ym <= 2) { + (k == 0 ? MaskB : MaskA)[i] |= (0x80 >> b); + } + ym = (ym + 2) % 4; xm = (xm + 2) % 4; + } + } + } + Init = true; + } + + switch(pattern) { + case Canvas::FillPattern::SOLID: + glDisable(GL_POLYGON_STIPPLE); + break; + + case Canvas::FillPattern::CHECKERED_A: + glEnable(GL_POLYGON_STIPPLE); + glPolygonStipple(MaskA); + break; + + case Canvas::FillPattern::CHECKERED_B: + glEnable(GL_POLYGON_STIPPLE); + glPolygonStipple(MaskB); + break; + } +} + +//----------------------------------------------------------------------------- +// OpenGL 1 / compatibility profile based renderer +//----------------------------------------------------------------------------- + +class OpenGl1Renderer final : public ViewportCanvas { +public: + Camera camera; + Lighting lighting; + // Cached OpenGL state. + struct { + bool drawing; + GLenum mode; + hStroke hcs; + Stroke *stroke; + hFill hcf; + Fill *fill; + std::weak_ptr texture; + } current; + + // List-initialize current to work around MSVC bug 746973. + OpenGl1Renderer() : camera(), lighting(), current({}) {} + + const Camera &GetCamera() const override { return camera; } + + void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; + void DrawEdges(const SEdgeList &el, hStroke hcs) override; + bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; } + void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override; + void DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) override; + + void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) override; + void DrawPoint(const Vector &o, hStroke hcs) override; + void DrawPolygon(const SPolygon &p, hFill hcf) override; + void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) override; + void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override; + void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) override; + void InvalidatePixmap(std::shared_ptr pm) override; + + void SelectPrimitive(unsigned mode); + void UnSelectPrimitive(); + Stroke *SelectStroke(hStroke hcs); + Fill *SelectFill(hFill hcf); + void SelectTexture(std::shared_ptr pm); + void DoFatLineEndcap(const Vector &p, const Vector &u, const Vector &v); + void DoFatLine(const Vector &a, const Vector &b, double width); + void DoLine(const Vector &a, const Vector &b, hStroke hcs); + void DoPoint(Vector p, double radius); + void DoStippledLine(const Vector &a, const Vector &b, hStroke hcs, double phase = 0.0); + + void UpdateProjection(); + void SetCamera(const Camera &camera) override; + void SetLighting(const Lighting &lighting) override; + + void StartFrame() override; + void FlushFrame() override; + void FinishFrame() override; + std::shared_ptr ReadFrame() override; + + void GetIdent(const char **vendor, const char **renderer, const char **version) override; +}; + +//----------------------------------------------------------------------------- +// A simple OpenGL state tracker to group consecutive draw calls. +//----------------------------------------------------------------------------- + +void OpenGl1Renderer::SelectPrimitive(GLenum mode) { + if(current.drawing && current.mode == mode) { + return; + } else if(current.drawing) { + glEnd(); + } + glBegin(mode); + current.drawing = true; + current.mode = mode; +} + +void OpenGl1Renderer::UnSelectPrimitive() { + if(!current.drawing) return; + glEnd(); + current.drawing = false; +} + +Canvas::Stroke *OpenGl1Renderer::SelectStroke(hStroke hcs) { + if(current.hcs == hcs) return current.stroke; + + Stroke *stroke = strokes.FindById(hcs); + UnSelectPrimitive(); + ssglColorRGBA(stroke->color); + ssglDepthRange(stroke->layer, stroke->zIndex); + ssglLineWidth(stroke->WidthPx(camera)); + // Fat lines and points are quads affected by glPolygonStipple, so make sure + // they are displayed correctly. + ssglFillPattern(FillPattern::SOLID); + glDisable(GL_TEXTURE_2D); + + current.hcs = hcs; + current.stroke = stroke; + current.hcf = {}; + current.fill = NULL; + current.texture.reset(); + return stroke; +} + +Canvas::Fill *OpenGl1Renderer::SelectFill(hFill hcf) { + if(current.hcf == hcf) return current.fill; + + Fill *fill = fills.FindById(hcf); + UnSelectPrimitive(); + ssglColorRGBA(fill->color); + ssglDepthRange(fill->layer, fill->zIndex); + ssglFillPattern(fill->pattern); + glDisable(GL_TEXTURE_2D); + + current.hcs = {}; + current.stroke = NULL; + current.hcf = hcf; + current.fill = fill; + current.texture.reset(); + return fill; +} + +static int RoundUpToPowerOfTwo(int v) +{ + for(int i = 0; i < 31; i++) { + int vt = (1 << i); + if(vt >= v) { + return vt; + } + } + return 0; +} + +void OpenGl1Renderer::SelectTexture(std::shared_ptr pm) { + if(current.texture.lock() == pm) return; + + glBindTexture(GL_TEXTURE_2D, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + GLenum format = 0; + switch(pm->format) { + case Pixmap::Format::RGBA: format = GL_RGBA; break; + case Pixmap::Format::RGB: format = GL_RGB; break; + case Pixmap::Format::A: format = GL_ALPHA; break; + case Pixmap::Format::BGRA: + case Pixmap::Format::BGR: + ssassert(false, "Unexpected pixmap format"); + } + + if(!HasGl1V1Quirk()) { + glTexImage2D(GL_TEXTURE_2D, 0, format, pm->width, pm->height, 0, + format, GL_UNSIGNED_BYTE, &pm->data[0]); + } else { + GLsizei width = RoundUpToPowerOfTwo(pm->width); + GLsizei height = RoundUpToPowerOfTwo(pm->height); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, + format, GL_UNSIGNED_BYTE, 0); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pm->width, pm->height, + format, GL_UNSIGNED_BYTE, &pm->data[0]); + } + + glEnable(GL_TEXTURE_2D); + + current.texture = pm; +} + +//----------------------------------------------------------------------------- +// OpenGL's GL_LINES mode does not work on lines thicker than about 3 px, +// so we have to draw thicker lines using triangles. +//----------------------------------------------------------------------------- + +void OpenGl1Renderer::DoFatLineEndcap(const Vector &p, const Vector &u, const Vector &v) { + // A table of cos and sin of (pi*i/10 + pi/2), as i goes from 0 to 10 + static const double Circle[11][2] = { + { 0.0000, 1.0000 }, + { -0.3090, 0.9511 }, + { -0.5878, 0.8090 }, + { -0.8090, 0.5878 }, + { -0.9511, 0.3090 }, + { -1.0000, 0.0000 }, + { -0.9511, -0.3090 }, + { -0.8090, -0.5878 }, + { -0.5878, -0.8090 }, + { -0.3090, -0.9511 }, + { 0.0000, -1.0000 }, + }; + + SelectPrimitive(GL_TRIANGLE_FAN); + for(auto pc : Circle) { + double c = pc[0], s = pc[1]; + ssglVertex3v(p.Plus(u.ScaledBy(c)).Plus(v.ScaledBy(s))); + } + UnSelectPrimitive(); +} + +void OpenGl1Renderer::DoFatLine(const Vector &a, const Vector &b, double width) { + // The half-width of the line we're drawing. + double hw = width / 2; + Vector ab = b.Minus(a); + Vector gn = (camera.projRight).Cross(camera.projUp); + Vector abn = (ab.Cross(gn)).WithMagnitude(1); + abn = abn.Minus(gn.ScaledBy(gn.Dot(abn))); + // So now abn is normal to the projection of ab into the screen, so the + // line will always have constant thickness as the view is rotated. + + abn = abn.WithMagnitude(hw); + ab = gn.Cross(abn); + ab = ab. WithMagnitude(hw); + + // The body of a line is a quad + SelectPrimitive(GL_QUADS); + ssglVertex3v(a.Plus (abn)); + ssglVertex3v(b.Plus (abn)); + ssglVertex3v(b.Minus(abn)); + ssglVertex3v(a.Minus(abn)); + // And the line has two semi-circular end caps. + DoFatLineEndcap(a, ab, abn); + DoFatLineEndcap(b, ab.ScaledBy(-1), abn); +} + +void OpenGl1Renderer::DoLine(const Vector &a, const Vector &b, hStroke hcs) { + if(a.Equals(b)) return; + + Stroke *stroke = SelectStroke(hcs); + if(stroke->WidthPx(camera) <= 3.0) { + SelectPrimitive(GL_LINES); + ssglVertex3v(a); + ssglVertex3v(b); + } else { + DoFatLine(a, b, stroke->WidthPx(camera) / camera.scale); + } +} + +void OpenGl1Renderer::DoPoint(Vector p, double d) { + if(d <= 3.0) { + Vector u = camera.projRight.WithMagnitude(d / 2.0 / camera.scale); + + SelectPrimitive(GL_LINES); + ssglVertex3v(p.Minus(u)); + ssglVertex3v(p.Plus(u)); + } else { + Vector u = camera.projRight.WithMagnitude(d / 2.0 / camera.scale); + Vector v = camera.projUp.WithMagnitude(d / 2.0 / camera.scale); + + DoFatLineEndcap(p, u, v); + DoFatLineEndcap(p, u.ScaledBy(-1.0), v); + } +} + +void OpenGl1Renderer::DoStippledLine(const Vector &a, const Vector &b, hStroke hcs, double phase) { + Stroke *stroke = SelectStroke(hcs); + + if(stroke->stipplePattern == StipplePattern::CONTINUOUS) { + DoLine(a, b, hcs); + return; + } + + double scale = stroke->StippleScaleMm(camera); + const std::vector &dashes = StipplePatternDashes(stroke->stipplePattern); + double length = StipplePatternLength(stroke->stipplePattern) * scale; + + phase -= floor(phase / length) * length; + + double curPhase = 0.0; + size_t curDash; + for(curDash = 0; curDash < dashes.size(); curDash++) { + curPhase += dashes[curDash] * scale; + if(phase < curPhase) break; + } + + Vector dir = b.Minus(a); + double len = dir.Magnitude(); + dir = dir.WithMagnitude(1.0); + + double cur = 0.0; + Vector curPos = a; + double width = stroke->WidthMm(camera); + + double curDashLen = (curPhase - phase) / scale; + while(cur < len) { + double next = std::min(len, cur + curDashLen * scale); + Vector nextPos = curPos.Plus(dir.ScaledBy(next - cur)); + if(curDash % 2 == 0) { + if(curDashLen <= LENGTH_EPS) { + DoPoint(curPos, width); + } else { + DoLine(curPos, nextPos, hcs); + } + } + cur = next; + curPos = nextPos; + curDash++; + curDashLen = dashes[curDash % dashes.size()]; + } +} + +//----------------------------------------------------------------------------- +// A canvas implemented using OpenGL 3 immediate mode. +//----------------------------------------------------------------------------- + +void OpenGl1Renderer::DrawLine(const Vector &a, const Vector &b, hStroke hcs) { + DoStippledLine(a, b, hcs); +} + +void OpenGl1Renderer::DrawEdges(const SEdgeList &el, hStroke hcs) { + double phase = 0.0; + for(const SEdge *e = el.l.First(); e; e = el.l.NextAfter(e)) { + DoStippledLine(e->a, e->b, hcs, phase); + phase += e->a.Minus(e->b).Magnitude(); + } +} + +void OpenGl1Renderer::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) { + Vector projDir = camera.projRight.Cross(camera.projUp); + double phase = 0.0; + switch(drawAs) { + case DrawOutlinesAs::EMPHASIZED_AND_CONTOUR: + for(const SOutline &o : ol.l) { + if(o.IsVisible(projDir) || o.tag != 0) { + DoStippledLine(o.a, o.b, hcs, phase); + } + phase += o.a.Minus(o.b).Magnitude(); + } + break; + + case DrawOutlinesAs::EMPHASIZED_WITHOUT_CONTOUR: + for(const SOutline &o : ol.l) { + if(!o.IsVisible(projDir) && o.tag != 0) { + DoStippledLine(o.a, o.b, hcs, phase); + } + phase += o.a.Minus(o.b).Magnitude(); + } + break; + + case DrawOutlinesAs::CONTOUR_ONLY: + for(const SOutline &o : ol.l) { + if(o.IsVisible(projDir)) { + DoStippledLine(o.a, o.b, hcs, phase); + } + phase += o.a.Minus(o.b).Magnitude(); + } + break; + } +} + +void OpenGl1Renderer::DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) { + auto traceEdge = [&](Vector a, Vector b) { DoStippledLine(a, b, hcs); }; + VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera); +} + +void OpenGl1Renderer::DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) { + SelectFill(hcf); + SelectPrimitive(GL_QUADS); + ssglVertex3v(a); + ssglVertex3v(b); + ssglVertex3v(c); + ssglVertex3v(d); +} + +void OpenGl1Renderer::DrawPoint(const Vector &o, Canvas::hStroke hcs) { + Stroke *stroke = SelectStroke(hcs); + + Canvas::Fill fill = {}; + fill.layer = stroke->layer; + fill.zIndex = stroke->zIndex; + fill.color = stroke->color; + hFill hcf = GetFill(fill); + + Vector r = camera.projRight.ScaledBy(stroke->width/2.0/camera.scale); + Vector u = camera.projUp.ScaledBy(stroke->width/2.0/camera.scale); + Vector a = o.Plus (r).Plus (u), + b = o.Plus (r).Minus(u), + c = o.Minus(r).Minus(u), + d = o.Minus(r).Plus (u); + DrawQuad(a, b, c, d, hcf); +} + +#ifdef WIN32 +#define SSGL_CALLBACK CALLBACK +#else +#define SSGL_CALLBACK +#endif +typedef void(SSGL_CALLBACK *GLUCallback)(); + +static void SSGL_CALLBACK Vertex(Vector *p) { + ssglVertex3v(*p); +} +static void SSGL_CALLBACK Combine(double coords[3], void *vertexData[4], + float weight[4], void **outData) { + Vector *n = (Vector *)AllocTemporary(sizeof(Vector)); + n->x = coords[0]; + n->y = coords[1]; + n->z = coords[2]; + + *outData = n; +} +void OpenGl1Renderer::DrawPolygon(const SPolygon &p, hFill hcf) { + UnSelectPrimitive(); + SelectFill(hcf); + + GLUtesselator *gt = gluNewTess(); + gluTessCallback(gt, GLU_TESS_BEGIN, (GLUCallback) glBegin); + gluTessCallback(gt, GLU_TESS_VERTEX, (GLUCallback) Vertex); + gluTessCallback(gt, GLU_TESS_END, (GLUCallback) glEnd); + gluTessCallback(gt, GLU_TESS_COMBINE, (GLUCallback) Combine); + + gluTessProperty(gt, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); + + ssglNormal3v(p.normal); + gluTessNormal(gt, p.normal.x, p.normal.y, p.normal.z); + + gluTessBeginPolygon(gt, NULL); + for(const SContour &sc : p.l) { + gluTessBeginContour(gt); + for(const SPoint &sp : sc.l) { + double ap[3] = { sp.p.x, sp.p.y, sp.p.z }; + gluTessVertex(gt, ap, (GLvoid *) &sp.p); + } + gluTessEndContour(gt); + } + gluTessEndPolygon(gt); + + gluDeleteTess(gt); +} + +void OpenGl1Renderer::DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) { + UnSelectPrimitive(); + + RgbaColor frontColor = {}, + backColor = {}; + + Fill *frontFill = SelectFill(hcfFront); + frontColor = frontFill->color; + + ssglMaterialRGBA(GL_FRONT, frontFill->color); + if(hcfBack.v != 0) { + Fill *backFill = fills.FindById(hcfBack); + backColor = backFill->color; + ssassert(frontFill->layer == backFill->layer && + frontFill->zIndex == backFill->zIndex, + "frontFill and backFill should belong to the same depth range"); + ssassert(frontFill->pattern == backFill->pattern, + "frontFill and backFill should have the same pattern"); + glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1); + ssglMaterialRGBA(GL_BACK, backFill->color); + } else { + glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0); + } + + RgbaColor triangleColor = {}; + glEnable(GL_LIGHTING); + glBegin(GL_TRIANGLES); + for(const STriangle &tr : m.l) { + if(frontColor.IsEmpty() || backColor.IsEmpty()) { + if(triangleColor.IsEmpty() || !triangleColor.Equals(tr.meta.color)) { + triangleColor = tr.meta.color; + if(frontColor.IsEmpty()) { + ssglMaterialRGBA(GL_FRONT, triangleColor); + } + if(backColor.IsEmpty()) { + ssglMaterialRGBA(GL_BACK, triangleColor); + } + } + } + + if(tr.an.EqualsExactly(Vector::From(0, 0, 0))) { + // Compute the normal from the vertices + ssglNormal3v(tr.Normal()); + ssglVertex3v(tr.a); + ssglVertex3v(tr.b); + ssglVertex3v(tr.c); + } else { + // Use the exact normals that are specified + ssglNormal3v(tr.an); + ssglVertex3v(tr.a); + ssglNormal3v(tr.bn); + ssglVertex3v(tr.b); + ssglNormal3v(tr.cn); + ssglVertex3v(tr.c); + } + } + glEnd(); + glDisable(GL_LIGHTING); +} + +void OpenGl1Renderer::DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) { + SelectFill(hcf); + SelectPrimitive(GL_TRIANGLES); + size_t facesSize = faces.size(); + for(const STriangle &tr : m.l) { + uint32_t face = tr.meta.face; + for(size_t j = 0; j < facesSize; j++) { + if(faces[j] != face) continue; + ssglVertex3v(tr.a); + ssglVertex3v(tr.b); + ssglVertex3v(tr.c); + break; + } + } +} + +void OpenGl1Renderer::DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) { + double xfactor = 1.0, + yfactor = 1.0; + if(HasGl1V1Quirk()) { + xfactor = (double)pm->width / RoundUpToPowerOfTwo(pm->width); + yfactor = (double)pm->height / RoundUpToPowerOfTwo(pm->height); + } + + UnSelectPrimitive(); + SelectFill(hcf); + SelectTexture(pm); + SelectPrimitive(GL_QUADS); + glTexCoord2d(ta.x * xfactor, ta.y * yfactor); + ssglVertex3v(o); + glTexCoord2d(ta.x * xfactor, tb.y * yfactor); + ssglVertex3v(o.Plus(v)); + glTexCoord2d(tb.x * xfactor, tb.y * yfactor); + ssglVertex3v(o.Plus(u).Plus(v)); + glTexCoord2d(tb.x * xfactor, ta.y * yfactor); + ssglVertex3v(o.Plus(u)); +} + +void OpenGl1Renderer::InvalidatePixmap(std::shared_ptr pm) { + if(current.texture.lock() == pm) { + current.texture.reset(); + } +} + +void OpenGl1Renderer::UpdateProjection() { + UnSelectPrimitive(); + + glViewport(0, 0, + (GLsizei)(camera.width * camera.pixelRatio), + (GLsizei)(camera.height * camera.pixelRatio)); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glScaled(camera.scale * 2.0 / camera.width, + camera.scale * 2.0 / camera.height, + camera.scale * 1.0 / 30000); + + double mat[16]; + // Last thing before display is to apply the perspective + double clp = camera.tangent * camera.scale; + MakeMatrix(mat, 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, clp, 1); + glMultMatrixd(mat); + + // Before that, we apply the rotation + Vector projRight = camera.projRight, + projUp = camera.projUp, + n = camera.projUp.Cross(camera.projRight); + MakeMatrix(mat, projRight.x, projRight.y, projRight.z, 0, + projUp.x, projUp.y, projUp.z, 0, + n.x, n.y, n.z, 0, + 0, 0, 0, 1); + glMultMatrixd(mat); + + // And before that, the translation + Vector offset = camera.offset; + MakeMatrix(mat, 1, 0, 0, offset.x, + 0, 1, 0, offset.y, + 0, 0, 1, offset.z, + 0, 0, 0, 1); + glMultMatrixd(mat); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glClearDepth(1.0); + glClear(GL_DEPTH_BUFFER_BIT); +} + +void OpenGl1Renderer::StartFrame() { + glEnable(GL_NORMALIZE); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + glEnable(GL_LINE_SMOOTH); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + // don't enable GL_POLYGON_SMOOTH; that looks ugly on some graphics cards, + // drawn with leaks in the mesh + + glDepthFunc(GL_LEQUAL); + glEnable(GL_DEPTH_TEST); + + if(EXACT(lighting.lightIntensity[0] != 0.0)) { + glEnable(GL_LIGHT0); + GLfloat f = (GLfloat)lighting.lightIntensity[0]; + GLfloat li0[] = { f, f, f, 1.0f }; + glLightfv(GL_LIGHT0, GL_DIFFUSE, li0); + glLightfv(GL_LIGHT0, GL_SPECULAR, li0); + + Vector ld = camera.VectorFromProjs(lighting.lightDirection[0]); + GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 }; + glLightfv(GL_LIGHT0, GL_POSITION, ld0); + } + + if(EXACT(lighting.lightIntensity[1] != 0.0)) { + glEnable(GL_LIGHT1); + GLfloat f = (GLfloat)lighting.lightIntensity[1]; + GLfloat li0[] = { f, f, f, 1.0f }; + glLightfv(GL_LIGHT1, GL_DIFFUSE, li0); + glLightfv(GL_LIGHT1, GL_SPECULAR, li0); + + Vector ld = camera.VectorFromProjs(lighting.lightDirection[1]); + GLfloat ld0[4] = { (GLfloat)ld.x, (GLfloat)ld.y, (GLfloat)ld.z, 0 }; + glLightfv(GL_LIGHT1, GL_POSITION, ld0); + } + + if(EXACT(lighting.ambientIntensity != 0.0)) { + GLfloat ambient[4] = { (float)lighting.ambientIntensity, + (float)lighting.ambientIntensity, + (float)lighting.ambientIntensity, 1 }; + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); + } + + glClearColor(lighting.backgroundColor.redF(), lighting.backgroundColor.greenF(), + lighting.backgroundColor.blueF(), lighting.backgroundColor.alphaF()); + glClear(GL_COLOR_BUFFER_BIT); + + glClearDepth(1.0); + glClear(GL_DEPTH_BUFFER_BIT); +} + +void OpenGl1Renderer::FlushFrame() { + UnSelectPrimitive(); + + glFlush(); +} + +void OpenGl1Renderer::FinishFrame() { + glFinish(); + + GLenum error = glGetError(); + if(error != GL_NO_ERROR) { + dbp("glGetError() == 0x%X %s", error, gluErrorString(error)); + } +} + +std::shared_ptr OpenGl1Renderer::ReadFrame() { + int width = (int)(camera.width * camera.pixelRatio); + int height = (int)(camera.height * camera.pixelRatio); + std::shared_ptr pixmap = + Pixmap::Create(Pixmap::Format::RGB, (size_t)width, (size_t)height); + glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, &pixmap->data[0]); + ssassert(glGetError() == GL_NO_ERROR, "Unexpected glReadPixels error"); + return pixmap; +} + +void OpenGl1Renderer::GetIdent(const char **vendor, const char **renderer, const char **version) { + *vendor = (const char *)glGetString(GL_VENDOR); + *renderer = (const char *)glGetString(GL_RENDERER); + *version = (const char *)glGetString(GL_VERSION); +} + +void OpenGl1Renderer::SetCamera(const Camera &c) { + camera = c; + UpdateProjection(); +} + +void OpenGl1Renderer::SetLighting(const Lighting &l) { + lighting = l; +} + +std::shared_ptr CreateRenderer() { + return std::shared_ptr(new OpenGl1Renderer()); +} + +} diff --git a/src/render/rendergl3.cpp b/src/render/rendergl3.cpp new file mode 100644 index 0000000..4816496 --- /dev/null +++ b/src/render/rendergl3.cpp @@ -0,0 +1,1102 @@ +//----------------------------------------------------------------------------- +// OpenGL ES 2.0 and OpenGL 3.0 based rendering interface. +// +// Copyright 2016 Aleksey Egorov +//----------------------------------------------------------------------------- +#include "solvespace.h" +#include "gl3shader.h" + +namespace SolveSpace { + +class TextureCache { +public: + std::map, GLuint, + std::owner_less>> items; + + bool Lookup(std::shared_ptr ptr, GLuint *result) { + auto it = items.find(ptr); + if(it == items.end()) { + GLuint id; + glGenTextures(1, &id); + items[ptr] = id; + *result = id; + return false; + } + + *result = it->second; + return true; + } + + void CleanupUnused() { + for(auto it = items.begin(); it != items.end();) { + if(it->first.expired()) { + glDeleteTextures(1, &it->second); + it = items.erase(it); + continue; + } + it++; + } + } +}; + +// A canvas that uses the core OpenGL 3 profile, for desktop systems. +class OpenGl3Renderer final : public ViewportCanvas { +public: + struct SEdgeListItem { + hStroke h; + SEdgeList lines; + + void Clear() { lines.Clear(); } + }; + + struct SMeshListItem { + hFill h; + SIndexedMesh mesh; + + void Clear() { mesh.Clear(); } + }; + + struct SPointListItem { + hStroke h; + SIndexedMesh points; + + void Clear() { points.Clear(); } + }; + + IdList lines; + IdList meshes; + IdList points; + + TextureCache pixmapCache; + std::shared_ptr masks[3]; + + bool initialized; + StippleAtlas atlas; + MeshRenderer meshRenderer; + IndexedMeshRenderer imeshRenderer; + EdgeRenderer edgeRenderer; + OutlineRenderer outlineRenderer; + + Camera camera; + Lighting lighting; + // Cached OpenGL state. + struct { + hStroke hcs; + Stroke *stroke; + hFill hcf; + Fill *fill; + std::weak_ptr texture; + } current; + const char *vendor = ""; + const char *renderer = ""; + const char *version = ""; + + // List-initialize current to work around MSVC bug 746973. + OpenGl3Renderer() : + lines(), meshes(), points(), pixmapCache(), masks(), + initialized(), atlas(), meshRenderer(), imeshRenderer(), + edgeRenderer(), outlineRenderer(), camera(), lighting(), + current({}) {} + + void Init(); + + const Camera &GetCamera() const override { return camera; } + + void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; + void DrawEdges(const SEdgeList &el, hStroke hcs) override; + bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; } + void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs mode) override; + void DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) override; + + void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) override; + void DrawPoint(const Vector &o, hStroke hs) override; + void DrawPolygon(const SPolygon &p, hFill hcf) override; + void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) override; + void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override; + void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) override; + void InvalidatePixmap(std::shared_ptr pm) override; + + std::shared_ptr CreateBatch() override; + + Stroke *SelectStroke(hStroke hcs); + Fill *SelectFill(hFill hcf); + void SelectMask(FillPattern pattern); + void SelectTexture(std::shared_ptr pm); + void DoFatLineEndcap(const Vector &p, const Vector &u, const Vector &v); + void DoFatLine(const Vector &a, const Vector &b, double width); + void DoLine(const Vector &a, const Vector &b, hStroke hcs); + void DoPoint(Vector p, hStroke hs); + void DoStippledLine(const Vector &a, const Vector &b, hStroke hcs); + + void UpdateProjection(); + void SetCamera(const Camera &c) override; + void SetLighting(const Lighting &l) override; + + void StartFrame() override; + void FlushFrame() override; + void FinishFrame() override; + void Clear() override; + std::shared_ptr ReadFrame() override; + + void GetIdent(const char **vendor, const char **renderer, const char **version) override; +}; + +//----------------------------------------------------------------------------- +// Thin wrappers around OpenGL functions to fix bugs, adapt them to our +// data structures, etc. +//----------------------------------------------------------------------------- + +static void ssglDepthRange(Canvas::Layer layer, int zIndex) { + switch(layer) { + case Canvas::Layer::NORMAL: + case Canvas::Layer::FRONT: + case Canvas::Layer::BACK: + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + break; + + case Canvas::Layer::DEPTH_ONLY: + glDepthFunc(GL_LEQUAL); + glDepthMask(GL_TRUE); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + break; + + case Canvas::Layer::OCCLUDED: + glDepthFunc(GL_GREATER); + glDepthMask(GL_FALSE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + break; + } + + switch(layer) { + case Canvas::Layer::FRONT: + glDepthRangef(0.0f, 0.0f); + break; + + case Canvas::Layer::BACK: + glDepthRangef(1.0f, 1.0f); + break; + + case Canvas::Layer::NORMAL: + case Canvas::Layer::DEPTH_ONLY: + case Canvas::Layer::OCCLUDED: + // The size of this step depends on the resolution of the Z buffer; for + // a 16-bit buffer, this should be fine. + double offset = 1.0 / (65535 * 0.8) * zIndex; + glDepthRangef((float)(0.1 - offset), (float)(1.0 - offset)); + break; + } +} + +//----------------------------------------------------------------------------- +// A simple OpenGL state tracker to group consecutive draw calls. +//----------------------------------------------------------------------------- + +Canvas::Stroke *OpenGl3Renderer::SelectStroke(hStroke hcs) { + if(current.hcs == hcs) return current.stroke; + + Stroke *stroke = strokes.FindById(hcs); + ssglDepthRange(stroke->layer, stroke->zIndex); + + current.hcs = hcs; + current.stroke = stroke; + current.hcf = {}; + current.fill = NULL; + current.texture.reset(); + return stroke; +} + +void OpenGl3Renderer::SelectMask(FillPattern pattern) { + if(!masks[0]) { + masks[0] = Pixmap::Create(Pixmap::Format::A, 32, 32); + masks[1] = Pixmap::Create(Pixmap::Format::A, 32, 32); + masks[2] = Pixmap::Create(Pixmap::Format::A, 32, 32); + + for(int x = 0; x < 32; x++) { + for(int y = 0; y < 32; y++) { + masks[0]->data[y * 32 + x] = ((x / 2) % 2 == 0 && (y / 2) % 2 == 0) ? 0xFF : 0x00; + masks[1]->data[y * 32 + x] = ((x / 2) % 2 == 1 && (y / 2) % 2 == 1) ? 0xFF : 0x00; + masks[2]->data[y * 32 + x] = 0xFF; + } + } + } + + switch(pattern) { + case Canvas::FillPattern::SOLID: + SelectTexture(masks[2]); + break; + + case Canvas::FillPattern::CHECKERED_A: + SelectTexture(masks[0]); + break; + + case Canvas::FillPattern::CHECKERED_B: + SelectTexture(masks[1]); + break; + + default: ssassert(false, "Unexpected fill pattern"); + } +} + +Canvas::Fill *OpenGl3Renderer::SelectFill(hFill hcf) { + if(current.hcf == hcf) return current.fill; + + Fill *fill = fills.FindById(hcf); + ssglDepthRange(fill->layer, fill->zIndex); + + current.hcs = {}; + current.stroke = NULL; + current.hcf = hcf; + current.fill = fill; + if(fill->pattern != FillPattern::SOLID) { + SelectMask(fill->pattern); + } else if(fill->texture) { + SelectTexture(fill->texture); + } else { + SelectMask(FillPattern::SOLID); + } + return fill; +} + +static bool IsPowerOfTwo(size_t n) { + return (n & (n - 1)) == 0; +} + +void OpenGl3Renderer::InvalidatePixmap(std::shared_ptr pm) { + GLuint id; + pixmapCache.Lookup(pm, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + if(IsPowerOfTwo(pm->width) && IsPowerOfTwo(pm->height)) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + GLenum format = 0; + switch(pm->format) { + case Pixmap::Format::RGBA: format = GL_RGBA; break; + case Pixmap::Format::RGB: format = GL_RGB; break; +#if defined(HAVE_GLES) + case Pixmap::Format::A: format = GL_ALPHA; break; +#else + case Pixmap::Format::A: format = GL_RED; break; +#endif + case Pixmap::Format::BGRA: + case Pixmap::Format::BGR: + ssassert(false, "Unexpected pixmap format"); + } + glTexImage2D(GL_TEXTURE_2D, 0, format, pm->width, pm->height, 0, + format, GL_UNSIGNED_BYTE, &pm->data[0]); +} + +void OpenGl3Renderer::SelectTexture(std::shared_ptr pm) { + if(current.texture.lock() == pm) return; + + GLuint id; + if(!pixmapCache.Lookup(pm, &id)) { + InvalidatePixmap(pm); + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, id); + current.texture = pm; +} + +void OpenGl3Renderer::DoLine(const Vector &a, const Vector &b, hStroke hcs) { + SEdgeListItem *eli = lines.FindByIdNoOops(hcs); + if(eli == NULL) { + SEdgeListItem item = {}; + item.h = hcs; + lines.Add(&item); + eli = lines.FindByIdNoOops(hcs); + } + + eli->lines.AddEdge(a, b); +} + +void OpenGl3Renderer::DoPoint(Vector p, hStroke hs) { + SPointListItem *pli = points.FindByIdNoOops(hs); + if(pli == NULL) { + SPointListItem item = {}; + item.h = hs; + points.Add(&item); + pli = points.FindByIdNoOops(hs); + } + + pli->points.AddPoint(p); +} + +void OpenGl3Renderer::DoStippledLine(const Vector &a, const Vector &b, hStroke hcs) { + Stroke *stroke = strokes.FindById(hcs); + if(stroke->stipplePattern != StipplePattern::FREEHAND && + stroke->stipplePattern != StipplePattern::ZIGZAG) + { + DoLine(a, b, hcs); + return; + } + + const char *patternSeq = NULL; + Stroke s = *stroke; + s.stipplePattern = StipplePattern::CONTINUOUS; + hcs = GetStroke(s); + switch(stroke->stipplePattern) { + case StipplePattern::CONTINUOUS: DoLine(a, b, hcs); return; + case StipplePattern::SHORT_DASH: patternSeq = "- "; break; + case StipplePattern::DASH: patternSeq = "- "; break; + case StipplePattern::LONG_DASH: patternSeq = "_ "; break; + case StipplePattern::DASH_DOT: patternSeq = "-."; break; + case StipplePattern::DASH_DOT_DOT: patternSeq = "-.."; break; + case StipplePattern::DOT: patternSeq = "."; break; + case StipplePattern::FREEHAND: patternSeq = "~"; break; + case StipplePattern::ZIGZAG: patternSeq = "~__"; break; + } + + Vector dir = b.Minus(a); + double len = dir.Magnitude(); + dir = dir.WithMagnitude(1.0); + + const char *si = patternSeq; + double end = len; + double ss = stroke->stippleScale / 2.0; + do { + double start = end; + switch(*si) { + case ' ': + end -= 1.0 * ss; + break; + + case '-': + start = max(start - 0.5 * ss, 0.0); + end = max(start - 2.0 * ss, 0.0); + if(start == end) break; + DoLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), hcs); + end = max(end - 0.5 * ss, 0.0); + break; + + case '_': + end = max(end - 4.0 * ss, 0.0); + DoLine(a.Plus(dir.ScaledBy(start)), a.Plus(dir.ScaledBy(end)), hcs); + break; + + case '.': + end = max(end - 0.5 * ss, 0.0); + if(end == 0.0) break; + DoPoint(a.Plus(dir.ScaledBy(end)), hcs); + end = max(end - 0.5 * ss, 0.0); + break; + + case '~': { + Vector ab = b.Minus(a); + Vector gn = (camera.projRight).Cross(camera.projUp); + Vector abn = (ab.Cross(gn)).WithMagnitude(1); + abn = abn.Minus(gn.ScaledBy(gn.Dot(abn))); + double pws = 2.0 * stroke->width / camera.scale; + + end = max(end - 0.5 * ss, 0.0); + Vector aa = a.Plus(dir.ScaledBy(start)); + Vector bb = a.Plus(dir.ScaledBy(end)) + .Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss))); + DoLine(aa, bb, hcs); + if(end == 0.0) break; + + start = end; + end = max(end - 1.0 * ss, 0.0); + aa = a.Plus(dir.ScaledBy(end)) + .Plus(abn.ScaledBy(pws)) + .Minus(abn.ScaledBy(2.0 * pws * (start - end) / ss)); + DoLine(bb, aa, hcs); + if(end == 0.0) break; + + start = end; + end = max(end - 0.5 * ss, 0.0); + bb = a.Plus(dir.ScaledBy(end)) + .Minus(abn.ScaledBy(pws)) + .Plus(abn.ScaledBy(pws * (start - end) / (0.5 * ss))); + DoLine(aa, bb, hcs); + break; + } + + default: ssassert(false, "Unexpected stipple pattern element"); + } + if(*(++si) == 0) si = patternSeq; + } while(end > 0.0); +} + +//----------------------------------------------------------------------------- +// A canvas implemented using OpenGL 3 vertex buffer objects. +//----------------------------------------------------------------------------- + +void OpenGl3Renderer::Init() { + atlas.Init(); + edgeRenderer.Init(&atlas); + outlineRenderer.Init(&atlas); + meshRenderer.Init(); + imeshRenderer.Init(); + + vendor = (const char *)glGetString(GL_VENDOR); + renderer = (const char *)glGetString(GL_RENDERER); + version = (const char *)glGetString(GL_VERSION); + +#if !defined(HAVE_GLES) && !defined(__APPLE__) + GLuint array; + glGenVertexArrays(1, &array); + glBindVertexArray(array); +#endif + UpdateProjection(); +} + +void OpenGl3Renderer::DrawLine(const Vector &a, const Vector &b, hStroke hcs) { + DoStippledLine(a, b, hcs); +} + +void OpenGl3Renderer::DrawEdges(const SEdgeList &el, hStroke hcs) { + for(const SEdge &e : el.l) { + DoStippledLine(e.a, e.b, hcs); + } +} + +void OpenGl3Renderer::DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs mode) { + if(ol.l.IsEmpty()) + return; + + Stroke *stroke = SelectStroke(hcs); + ssassert(stroke->stipplePattern != StipplePattern::ZIGZAG && + stroke->stipplePattern != StipplePattern::FREEHAND, + "ZIGZAG and FREEHAND not supported for outlines"); + + outlineRenderer.SetStroke(*stroke, 1.0 / camera.scale); + outlineRenderer.Draw(ol, mode); +} + +void OpenGl3Renderer::DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) { + SEdgeListItem *eli = lines.FindByIdNoOops(hcs); + if(eli == NULL) { + SEdgeListItem item = {}; + item.h = hcs; + lines.Add(&item); + eli = lines.FindByIdNoOops(hcs); + } + SEdgeList &lines = eli->lines; + auto traceEdge = [&](Vector a, Vector b) { lines.AddEdge(a, b); }; + VectorFont::Builtin()->Trace(height, o, u, v, text, traceEdge, camera); +} + +void OpenGl3Renderer::DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) { + SMeshListItem *li = meshes.FindByIdNoOops(hcf); + if(li == NULL) { + SMeshListItem item = {}; + item.h = hcf; + meshes.Add(&item); + li = meshes.FindByIdNoOops(hcf); + } + li->mesh.AddQuad(a, b, c, d); +} + +void OpenGl3Renderer::DrawPoint(const Vector &o, hStroke hs) { + DoPoint(o, hs); +} + +void OpenGl3Renderer::DrawPolygon(const SPolygon &p, hFill hcf) { + Fill *fill = SelectFill(hcf); + + SMesh m = {}; + p.TriangulateInto(&m); + meshRenderer.UseFilled(*fill); + meshRenderer.Draw(m); + m.Clear(); +} + +void OpenGl3Renderer::DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) { + ssassert(false, "Not implemented"); +} + +void OpenGl3Renderer::DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) { + if(faces.empty()) return; + + Fill *fill = SelectFill(hcf); + + SMesh facesMesh = {}; + for(uint32_t f : faces) { + for(const STriangle &t : m.l) { + if(f != t.meta.face) continue; + facesMesh.l.Add(&t); + } + } + + meshRenderer.UseFilled(*fill); + meshRenderer.Draw(facesMesh); + facesMesh.Clear(); +} + +void OpenGl3Renderer::DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) { + Fill fill = *fills.FindById(hcf); + fill.texture = pm; + hcf = GetFill(fill); + + SMeshListItem *mli = meshes.FindByIdNoOops(hcf); + if(mli == NULL) { + SMeshListItem item = {}; + item.h = hcf; + meshes.Add(&item); + mli = meshes.FindByIdNoOops(hcf); + } + + mli->mesh.AddPixmap(o, u, v, ta, tb); +} + +void OpenGl3Renderer::UpdateProjection() { + glViewport(0, 0, + (int)(camera.width * camera.pixelRatio), + (int)(camera.height * camera.pixelRatio)); + + double mat1[16]; + double mat2[16]; + + double sx = camera.scale * 2.0 / camera.width; + double sy = camera.scale * 2.0 / camera.height; + double sz = camera.scale * 1.0 / 30000; + + MakeMatrix(mat1, + sx, 0, 0, 0, + 0, sy, 0, 0, + 0, 0, sz, 0, + 0, 0, 0, 1 + ); + + // Last thing before display is to apply the perspective + double clp = camera.tangent * camera.scale; + MakeMatrix(mat2, + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, clp, 1 + ); + + double projection[16]; + MultMatrix(mat1, mat2, projection); + + // Before that, we apply the rotation + Vector u = camera.projRight, + v = camera.projUp, + n = camera.projUp.Cross(camera.projRight); + MakeMatrix(mat1, + u.x, u.y, u.z, 0, + v.x, v.y, v.z, 0, + n.x, n.y, n.z, 0, + 0, 0, 0, 1 + ); + + // And before that, the translation + Vector o = camera.offset; + MakeMatrix(mat2, + 1, 0, 0, o.x, + 0, 1, 0, o.y, + 0, 0, 1, o.z, + 0, 0, 0, 1 + ); + + double modelview[16]; + MultMatrix(mat1, mat2, modelview); + + imeshRenderer.SetProjection(projection); + imeshRenderer.SetModelview(modelview); + meshRenderer.SetProjection(projection); + meshRenderer.SetModelview(modelview); + edgeRenderer.SetProjection(projection); + edgeRenderer.SetModelview(modelview); + outlineRenderer.SetProjection(projection); + outlineRenderer.SetModelview(modelview); + + glClearDepthf(1.0f); + glClear(GL_DEPTH_BUFFER_BIT); +} + +void OpenGl3Renderer::StartFrame() { + if(!initialized) { + Init(); + initialized = true; + } + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + + glDepthFunc(GL_LEQUAL); + glEnable(GL_DEPTH_TEST); + + RgbaColor backgroundColor = lighting.backgroundColor; + glClearColor(backgroundColor.redF(), backgroundColor.greenF(), + backgroundColor.blueF(), backgroundColor.alphaF()); + glClearDepthf(1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glFrontFace(GL_CW); +} + +void OpenGl3Renderer::FlushFrame() { + for(SMeshListItem &li : meshes) { + Fill *fill = SelectFill(li.h); + + imeshRenderer.UseFilled(*fill); + imeshRenderer.Draw(li.mesh); + li.mesh.Clear(); + } + meshes.Clear(); + + for(SEdgeListItem &eli : lines) { + Stroke *stroke = SelectStroke(eli.h); + + edgeRenderer.SetStroke(*stroke, 1.0 / camera.scale); + edgeRenderer.Draw(eli.lines); + eli.lines.Clear(); + } + lines.Clear(); + + for(SPointListItem &li : points) { + Stroke *stroke = SelectStroke(li.h); + + imeshRenderer.UsePoint(*stroke, 1.0 / camera.scale); + imeshRenderer.Draw(li.points); + li.points.Clear(); + } + points.Clear(); + + glFlush(); +} + +void OpenGl3Renderer::FinishFrame() { + glFinish(); + + GLenum error = glGetError(); + if(error != GL_NO_ERROR) { + dbp("glGetError() == 0x%X", error); + } +} + +void OpenGl3Renderer::Clear() { + ViewportCanvas::Clear(); + pixmapCache.CleanupUnused(); +} + +std::shared_ptr OpenGl3Renderer::ReadFrame() { + int width = (int)(camera.width * camera.pixelRatio); + int height = (int)(camera.height * camera.pixelRatio); + std::shared_ptr pixmap = + Pixmap::Create(Pixmap::Format::RGBA, (size_t)width, (size_t)height); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, &pixmap->data[0]); + ssassert(glGetError() == GL_NO_ERROR, "Unexpected glReadPixels error"); + return pixmap; +} + +void OpenGl3Renderer::GetIdent(const char **vendor, const char **renderer, const char **version) { + *vendor = this->vendor; + *renderer = this->renderer; + *version = this->version; +} + +void OpenGl3Renderer::SetCamera(const Camera &c) { + camera = c; + if(initialized) { + UpdateProjection(); + } +} + +void OpenGl3Renderer::SetLighting(const Lighting &l) { + lighting = l; +} + +//----------------------------------------------------------------------------- +// A batch canvas implemented using OpenGL 3 vertex buffer objects. +//----------------------------------------------------------------------------- + +class DrawCall { +public: + virtual Canvas::Layer GetLayer() const = 0; + virtual int GetZIndex() const = 0; + + virtual void Draw(OpenGl3Renderer *renderer) = 0; + virtual void Remove(OpenGl3Renderer *renderer) = 0; +}; + +class EdgeDrawCall final : public DrawCall { +public: + // Key + Canvas::Stroke stroke; + // Data + EdgeRenderer::Handle handle; + + Canvas::Layer GetLayer() const override { return stroke.layer; } + int GetZIndex() const override { return stroke.zIndex; } + + static std::shared_ptr Create(OpenGl3Renderer *renderer, const SEdgeList &el, + Canvas::Stroke *stroke) { + EdgeDrawCall *dc = new EdgeDrawCall(); + dc->stroke = *stroke; + dc->handle = renderer->edgeRenderer.Add(el); + return std::shared_ptr(dc); + } + + void Draw(OpenGl3Renderer *renderer) override { + ssglDepthRange(stroke.layer, stroke.zIndex); + renderer->edgeRenderer.SetStroke(stroke, 1.0 / renderer->camera.scale); + renderer->edgeRenderer.Draw(handle); + } + + void Remove(OpenGl3Renderer *renderer) override { + renderer->edgeRenderer.Remove(handle); + } +}; + +class OutlineDrawCall final : public DrawCall { +public: + // Key + Canvas::Stroke stroke; + // Data + OutlineRenderer::Handle handle; + Canvas::DrawOutlinesAs drawAs; + + Canvas::Layer GetLayer() const override { return stroke.layer; } + int GetZIndex() const override { return stroke.zIndex; } + + static std::shared_ptr Create(OpenGl3Renderer *renderer, const SOutlineList &ol, + Canvas::Stroke *stroke, + Canvas::DrawOutlinesAs drawAs) { + OutlineDrawCall *dc = new OutlineDrawCall(); + dc->stroke = *stroke; + dc->handle = renderer->outlineRenderer.Add(ol); + dc->drawAs = drawAs; + return std::shared_ptr(dc); + } + + void Draw(OpenGl3Renderer *renderer) override { + ssglDepthRange(stroke.layer, stroke.zIndex); + renderer->outlineRenderer.SetStroke(stroke, 1.0 / renderer->camera.scale); + renderer->outlineRenderer.Draw(handle, drawAs); + } + + void Remove(OpenGl3Renderer *renderer) override { + renderer->outlineRenderer.Remove(handle); + } +}; + +class PointDrawCall final : public DrawCall { +public: + // Key + Canvas::Stroke stroke; + // Data + IndexedMeshRenderer::Handle handle; + + Canvas::Layer GetLayer() const override { return stroke.layer; } + int GetZIndex() const override { return stroke.zIndex; } + + static std::shared_ptr Create(OpenGl3Renderer *renderer, const SIndexedMesh &mesh, + Canvas::Stroke *stroke) { + PointDrawCall *dc = new PointDrawCall(); + dc->stroke = *stroke; + dc->handle = renderer->imeshRenderer.Add(mesh); + return std::shared_ptr(dc); + } + + void Draw(OpenGl3Renderer *renderer) override { + ssglDepthRange(stroke.layer, stroke.zIndex); + renderer->imeshRenderer.UsePoint(stroke, 1.0 / renderer->camera.scale); + renderer->imeshRenderer.Draw(handle); + } + + void Remove(OpenGl3Renderer *renderer) override { + renderer->imeshRenderer.Remove(handle); + } +}; + +class PixmapDrawCall final : public DrawCall { +public: + // Key + Canvas::Fill fill; + // Data + IndexedMeshRenderer::Handle handle; + + Canvas::Layer GetLayer() const override { return fill.layer; } + int GetZIndex() const override { return fill.zIndex; } + + static std::shared_ptr Create(OpenGl3Renderer *renderer, const SIndexedMesh &mesh, + Canvas::Fill *fill) { + PixmapDrawCall *dc = new PixmapDrawCall(); + dc->fill = *fill; + dc->handle = renderer->imeshRenderer.Add(mesh); + return std::shared_ptr(dc); + } + + void Draw(OpenGl3Renderer *renderer) override { + ssglDepthRange(fill.layer, fill.zIndex); + if(fill.pattern != Canvas::FillPattern::SOLID) { + renderer->SelectMask(fill.pattern); + } else if(fill.texture) { + renderer->SelectTexture(fill.texture); + } else { + renderer->SelectMask(Canvas::FillPattern::SOLID); + } + renderer->imeshRenderer.UseFilled(fill); + renderer->imeshRenderer.Draw(handle); + } + + void Remove(OpenGl3Renderer *renderer) override { + renderer->imeshRenderer.Remove(handle); + } +}; + +class MeshDrawCall final : public DrawCall { +public: + // Key + Canvas::Fill fillFront; + // Data + MeshRenderer::Handle handle; + Canvas::Fill fillBack; + bool hasFillBack; + bool isShaded; + + Canvas::Layer GetLayer() const override { return fillFront.layer; } + int GetZIndex() const override { return fillFront.zIndex; } + + static std::shared_ptr Create(OpenGl3Renderer *renderer, const SMesh &m, + Canvas::Fill *fillFront, Canvas::Fill *fillBack = NULL, + bool isShaded = false) { + MeshDrawCall *dc = new MeshDrawCall(); + dc->fillFront = *fillFront; + dc->handle = renderer->meshRenderer.Add(m); + dc->fillBack = *fillBack; + dc->isShaded = isShaded; + dc->hasFillBack = (fillBack != NULL); + return std::shared_ptr(dc); + } + + void DrawFace(OpenGl3Renderer *renderer, GLenum cullFace, const Canvas::Fill &fill) { + glCullFace(cullFace); + ssglDepthRange(fill.layer, fill.zIndex); + if(fill.pattern != Canvas::FillPattern::SOLID) { + renderer->SelectMask(fill.pattern); + } else if(fill.texture) { + renderer->SelectTexture(fill.texture); + } else { + renderer->SelectMask(Canvas::FillPattern::SOLID); + } + if(isShaded) { + renderer->meshRenderer.UseShaded(renderer->lighting); + } else { + renderer->meshRenderer.UseFilled(fill); + } + renderer->meshRenderer.Draw(handle, /*useColors=*/fill.color.IsEmpty(), fill.color); + } + + void Draw(OpenGl3Renderer *renderer) override { + glEnable(GL_CULL_FACE); + if(hasFillBack) + DrawFace(renderer, GL_BACK, fillBack); + DrawFace(renderer, GL_FRONT, fillFront); + glDisable(GL_CULL_FACE); + } + + void Remove(OpenGl3Renderer *renderer) override { + renderer->meshRenderer.Remove(handle); + } +}; + +struct CompareDrawCall { + bool operator()(const std::shared_ptr &a, const std::shared_ptr &b) const { + const Canvas::Layer stackup[] = { + Canvas::Layer::BACK, + Canvas::Layer::DEPTH_ONLY, + Canvas::Layer::NORMAL, + Canvas::Layer::OCCLUDED, + Canvas::Layer::FRONT + }; + + int aLayerIndex = + std::find(std::begin(stackup), std::end(stackup), a->GetLayer()) - std::begin(stackup); + int bLayerIndex = + std::find(std::begin(stackup), std::end(stackup), b->GetLayer()) - std::begin(stackup); + if(aLayerIndex == bLayerIndex) { + return a->GetZIndex() < b->GetZIndex(); + } else { + return aLayerIndex < bLayerIndex; + } + } +}; + +class OpenGl3RendererBatch final : public BatchCanvas { +public: + struct EdgeBuffer { + hStroke h; + SEdgeList edges; + + void Clear() { + edges.Clear(); + } + }; + + struct PointBuffer { + hStroke h; + SIndexedMesh points; + + void Clear() { + points.Clear(); + } + }; + + OpenGl3Renderer *renderer; + + IdList edgeBuffer; + IdList pointBuffer; + + std::multiset, CompareDrawCall> drawCalls; + + OpenGl3RendererBatch() : renderer(), edgeBuffer(), pointBuffer() {} + + void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override { + EdgeBuffer *eb = edgeBuffer.FindByIdNoOops(hcs); + if(!eb) { + EdgeBuffer neb = {}; + neb.h = hcs; + edgeBuffer.Add(&neb); + eb = edgeBuffer.FindById(hcs); + } + + eb->edges.AddEdge(a, b); + } + + void DrawEdges(const SEdgeList &el, hStroke hcs) override { + EdgeBuffer *eb = edgeBuffer.FindByIdNoOops(hcs); + if(!eb) { + EdgeBuffer neb = {}; + neb.h = hcs; + edgeBuffer.Add(&neb); + eb = edgeBuffer.FindById(hcs); + } + + for(const SEdge &e : el.l) { + eb->edges.AddEdge(e.a, e.b); + } + } + + bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { + return false; + } + + void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override { + drawCalls.emplace(OutlineDrawCall::Create(renderer, ol, strokes.FindById(hcs), drawAs)); + } + + void DrawVectorText(const std::string &text, double height, + const Vector &o, const Vector &u, const Vector &v, + hStroke hcs) override { + ssassert(false, "Not implemented"); + } + + void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, + hFill hcf) override { + ssassert(false, "Not implemented"); + } + + void DrawPoint(const Vector &o, hStroke hcs) override { + PointBuffer *pb = pointBuffer.FindByIdNoOops(hcs); + if(!pb) { + PointBuffer npb = {}; + npb.h = hcs; + pointBuffer.Add(&npb); + pb = pointBuffer.FindById(hcs); + } + + pb->points.AddPoint(o); + } + + void DrawPolygon(const SPolygon &p, hFill hcf) override { + SMesh m = {}; + p.TriangulateInto(&m); + drawCalls.emplace(MeshDrawCall::Create(renderer, m, fills.FindById(hcf), + fills.FindById(hcf))); + m.Clear(); + } + + void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {}) override { + drawCalls.emplace(MeshDrawCall::Create(renderer, m, fills.FindById(hcfFront), + fills.FindByIdNoOops(hcfBack), + /*isShaded=*/true)); + } + + void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override { + ssassert(false, "Not implemented"); + } + + void DrawPixmap(std::shared_ptr pm, + const Vector &o, const Vector &u, const Vector &v, + const Point2d &ta, const Point2d &tb, hFill hcf) override { + Fill fill = *fills.FindById(hcf); + fill.texture = pm; + hcf = GetFill(fill); + + SIndexedMesh mesh = {}; + mesh.AddPixmap(o, u, v, ta, tb); + drawCalls.emplace(PixmapDrawCall::Create(renderer, mesh, fills.FindByIdNoOops(hcf))); + mesh.Clear(); + } + + void InvalidatePixmap(std::shared_ptr pm) override { + ssassert(false, "Not implemented"); + } + + void Finalize() override { + for(const EdgeBuffer &eb : edgeBuffer) { + drawCalls.emplace(EdgeDrawCall::Create(renderer, eb.edges, strokes.FindById(eb.h))); + } + edgeBuffer.Clear(); + + for(const PointBuffer &pb : pointBuffer) { + drawCalls.emplace(PointDrawCall::Create(renderer, pb.points, strokes.FindById(pb.h))); + } + pointBuffer.Clear(); + } + + void Draw() override { + renderer->current = {}; + + for(std::shared_ptr dc : drawCalls) { + dc->Draw(renderer); + } + } + + void Clear() override { + for(std::shared_ptr dc : drawCalls) { + dc->Remove(renderer); + } + drawCalls.clear(); + } +}; + +//----------------------------------------------------------------------------- +// Factory functions. +//----------------------------------------------------------------------------- + +std::shared_ptr OpenGl3Renderer::CreateBatch() { + OpenGl3RendererBatch *batch = new OpenGl3RendererBatch(); + batch->renderer = this; + return std::shared_ptr(batch); +} + +std::shared_ptr CreateRenderer() { + return std::shared_ptr(new OpenGl3Renderer()); +} + +} diff --git a/src/request.cpp b/src/request.cpp index b770e06..3614cec 100644 --- a/src/request.cpp +++ b/src/request.cpp @@ -12,30 +12,30 @@ const hRequest Request::HREQUEST_REFERENCE_XY = { 1 }; const hRequest Request::HREQUEST_REFERENCE_YZ = { 2 }; const hRequest Request::HREQUEST_REFERENCE_ZX = { 3 }; -const EntReqTable::TableEntry EntReqTable::Table[] = { -// request type entity type pts xtra? norml dist description -{ Request::WORKPLANE, Entity::WORKPLANE, 1, false, true, false, "workplane" }, -{ Request::DATUM_POINT, 0, 1, false, false, false, "datum-point" }, -{ Request::LINE_SEGMENT, Entity::LINE_SEGMENT, 2, false, false, false, "line-segment" }, -{ Request::CUBIC, Entity::CUBIC, 4, true, false, false, "cubic-bezier" }, -{ Request::CUBIC_PERIODIC, Entity::CUBIC_PERIODIC, 3, true, false, false, "periodic-cubic" }, -{ Request::CIRCLE, Entity::CIRCLE, 1, false, true, true, "circle" }, -{ Request::ARC_OF_CIRCLE, Entity::ARC_OF_CIRCLE, 3, false, true, false, "arc-of-circle" }, -{ Request::TTF_TEXT, Entity::TTF_TEXT, 2, false, true, false, "ttf-text" }, -{ 0, 0, 0, false, false, false, 0 }, +struct EntReqMapping { + Request::Type reqType; + Entity::Type entType; + int points; + bool useExtraPoints; + bool hasNormal; + bool hasDistance; +}; +static const EntReqMapping EntReqMap[] = { +// request type entity type pts xtra? norml dist +{ Request::Type::WORKPLANE, Entity::Type::WORKPLANE, 1, false, true, false }, +{ Request::Type::DATUM_POINT, (Entity::Type)0, 1, false, false, false }, +{ Request::Type::LINE_SEGMENT, Entity::Type::LINE_SEGMENT, 2, false, false, false }, +{ Request::Type::CUBIC, Entity::Type::CUBIC, 4, true, false, false }, +{ Request::Type::CUBIC_PERIODIC, Entity::Type::CUBIC_PERIODIC, 3, true, false, false }, +{ Request::Type::CIRCLE, Entity::Type::CIRCLE, 1, false, true, true }, +{ Request::Type::ARC_OF_CIRCLE, Entity::Type::ARC_OF_CIRCLE, 3, false, true, false }, +{ Request::Type::TTF_TEXT, Entity::Type::TTF_TEXT, 4, false, true, false }, +{ Request::Type::IMAGE, Entity::Type::IMAGE, 4, false, true, false }, }; -const char *EntReqTable::DescriptionForRequest(int req) { - for(int i = 0; Table[i].reqType; i++) { - if(req == Table[i].reqType) { - return Table[i].description; - } - } - return "???"; -} - -void EntReqTable::CopyEntityInfo(const TableEntry *te, int extraPoints, - int *ent, int *req, int *pts, bool *hasNormal, bool *hasDistance) +static void CopyEntityInfo(const EntReqMapping *te, int extraPoints, + Entity::Type *ent, Request::Type *req, + int *pts, bool *hasNormal, bool *hasDistance) { int points = te->points; if(te->useExtraPoints) points += extraPoints; @@ -47,53 +47,82 @@ void EntReqTable::CopyEntityInfo(const TableEntry *te, int extraPoints, if(hasDistance) *hasDistance = te->hasDistance; } -bool EntReqTable::GetRequestInfo(int req, int extraPoints, - int *ent, int *pts, bool *hasNormal, bool *hasDistance) +bool EntReqTable::GetRequestInfo(Request::Type req, int extraPoints, + Entity::Type *ent, int *pts, bool *hasNormal, bool *hasDistance) { - for(int i = 0; Table[i].reqType; i++) { - const TableEntry *te = &(Table[i]); - if(req == te->reqType) { - CopyEntityInfo(te, extraPoints, - ent, NULL, pts, hasNormal, hasDistance); + for(const EntReqMapping &te : EntReqMap) { + if(req == te.reqType) { + CopyEntityInfo(&te, extraPoints, ent, NULL, pts, hasNormal, hasDistance); return true; } } return false; } -bool EntReqTable::GetEntityInfo(int ent, int extraPoints, - int *req, int *pts, bool *hasNormal, bool *hasDistance) +bool EntReqTable::GetEntityInfo(Entity::Type ent, int extraPoints, + Request::Type *req, int *pts, bool *hasNormal, bool *hasDistance) { - for(int i = 0; Table[i].reqType; i++) { - const TableEntry *te = &(Table[i]); - if(ent == te->entType) { - CopyEntityInfo(te, extraPoints, - NULL, req, pts, hasNormal, hasDistance); + for(const EntReqMapping &te : EntReqMap) { + if(ent == te.entType) { + CopyEntityInfo(&te, extraPoints, NULL, req, pts, hasNormal, hasDistance); return true; } } return false; } -int EntReqTable::GetRequestForEntity(int ent) { - int req = 0; - GetEntityInfo(ent, 0, &req, NULL, NULL, NULL); +Request::Type EntReqTable::GetRequestForEntity(Entity::Type ent) { + Request::Type req; + ssassert(GetEntityInfo(ent, 0, &req, NULL, NULL, NULL), + "No entity for request"); return req; } - void Request::Generate(IdList *entity, IdList *param) { int points = 0; - int et = 0; + Entity::Type et = (Entity::Type)0; bool hasNormal = false; bool hasDistance = false; int i; + // Request-specific generation. + switch(type) { + case Type::TTF_TEXT: { + double actualAspectRatio = SS.fonts.AspectRatio(font, str); + if(EXACT(actualAspectRatio != 0.0)) { + // We could load the font, so use the actual value. + aspectRatio = actualAspectRatio; + } + if(EXACT(aspectRatio == 0.0)) { + // We couldn't load the font and we don't have anything saved, + // so just use 1:1, which is valid for the missing font symbol anyhow. + aspectRatio = 1.0; + } + break; + } + + case Type::IMAGE: { + auto image = SS.images.find(file); + if(image != SS.images.end()) { + std::shared_ptr pixmap = (*image).second; + if(pixmap != NULL) { + aspectRatio = (double)pixmap->width / (double)pixmap->height; + } + } + if(EXACT(aspectRatio == 0.0)) { + aspectRatio = 1.0; + } + break; + } + + default: // most requests don't do anything else + break; + } + Entity e = {}; - EntReqTable::GetRequestInfo(type, extraPoints, - &et, &points, &hasNormal, &hasDistance); + EntReqTable::GetRequestInfo(type, extraPoints, &et, &points, &hasNormal, &hasDistance); // Generate the entity that's specific to this request. e.type = et; @@ -104,6 +133,8 @@ void Request::Generate(IdList *entity, e.construction = construction; e.str = str; e.font = font; + e.file = file; + e.aspectRatio = aspectRatio; e.h = h.entity(0); // And generate entities for the points @@ -111,18 +142,18 @@ void Request::Generate(IdList *entity, Entity p = {}; p.workplane = workplane; // points start from entity 1, except for datum point case - p.h = h.entity(i+(et ? 1 : 0)); + p.h = h.entity(i+((et != (Entity::Type)0) ? 1 : 0)); p.group = group; p.style = style; - - if(workplane.v == Entity::FREE_IN_3D.v) { - p.type = Entity::POINT_IN_3D; + p.construction = e.construction; + if(workplane == Entity::FREE_IN_3D) { + p.type = Entity::Type::POINT_IN_3D; // params for x y z p.param[0] = AddParam(param, h.param(16 + 3*i + 0)); p.param[1] = AddParam(param, h.param(16 + 3*i + 1)); p.param[2] = AddParam(param, h.param(16 + 3*i + 2)); } else { - p.type = Entity::POINT_IN_2D; + p.type = Entity::Type::POINT_IN_2D; // params for u v p.param[0] = AddParam(param, h.param(16 + 3*i + 0)); p.param[1] = AddParam(param, h.param(16 + 3*i + 1)); @@ -136,18 +167,19 @@ void Request::Generate(IdList *entity, n.h = h.entity(32); n.group = group; n.style = style; - if(workplane.v == Entity::FREE_IN_3D.v) { - n.type = Entity::NORMAL_IN_3D; + n.construction = e.construction; + if(workplane == Entity::FREE_IN_3D) { + n.type = Entity::Type::NORMAL_IN_3D; n.param[0] = AddParam(param, h.param(32+0)); n.param[1] = AddParam(param, h.param(32+1)); n.param[2] = AddParam(param, h.param(32+2)); n.param[3] = AddParam(param, h.param(32+3)); } else { - n.type = Entity::NORMAL_IN_2D; + n.type = Entity::Type::NORMAL_IN_2D; // and this is just a copy of the workplane quaternion, // so no params required } - if(points < 1) oops(); + ssassert(points >= 1, "Positioning a normal requires a point"); // The point determines where the normal gets displayed on-screen; // it's entirely cosmetic. n.point[0] = e.point[0]; @@ -160,36 +192,46 @@ void Request::Generate(IdList *entity, d.h = h.entity(64); d.group = group; d.style = style; - d.type = Entity::DISTANCE; + d.type = Entity::Type::DISTANCE; d.param[0] = AddParam(param, h.param(64)); entity->Add(&d); e.distance = d.h; } - if(et) entity->Add(&e); + if(et != (Entity::Type)0) entity->Add(&e); } -std::string Request::DescriptionString(void) { - const char *s; - if(h.v == Request::HREQUEST_REFERENCE_XY.v) { +std::string Request::DescriptionString() const { + const char *s = ""; + if(h == Request::HREQUEST_REFERENCE_XY) { s = "#XY"; - } else if(h.v == Request::HREQUEST_REFERENCE_YZ.v) { + } else if(h == Request::HREQUEST_REFERENCE_YZ) { s = "#YZ"; - } else if(h.v == Request::HREQUEST_REFERENCE_ZX.v) { + } else if(h == Request::HREQUEST_REFERENCE_ZX) { s = "#ZX"; } else { - s = EntReqTable::DescriptionForRequest(type); + switch(type) { + case Type::WORKPLANE: s = "workplane"; break; + case Type::DATUM_POINT: s = "datum-point"; break; + case Type::LINE_SEGMENT: s = "line-segment"; break; + case Type::CUBIC: s = "cubic-bezier"; break; + case Type::CUBIC_PERIODIC: s = "periodic-cubic"; break; + case Type::CIRCLE: s = "circle"; break; + case Type::ARC_OF_CIRCLE: s = "arc-of-circle"; break; + case Type::TTF_TEXT: s = "ttf-text"; break; + case Type::IMAGE: s = "image"; break; + } } - + ssassert(s != NULL, "Unexpected request type"); return ssprintf("r%03x-%s", h.v, s); } -int Request::IndexOfPoint(hEntity he) { - if(type == DATUM_POINT) { - return (he.v == h.entity(0).v) ? 0 : -1; +int Request::IndexOfPoint(hEntity he) const { + if(type == Type::DATUM_POINT) { + return (he == h.entity(0)) ? 0 : -1; } for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) { - if(he.v == h.entity(i + 1).v) { + if(he == h.entity(i + 1)) { return i; } } diff --git a/src/resource.cpp b/src/resource.cpp new file mode 100644 index 0000000..7b19081 --- /dev/null +++ b/src/resource.cpp @@ -0,0 +1,1583 @@ +//----------------------------------------------------------------------------- +// Discovery and loading of our resources (icons, fonts, templates, etc). +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include +#include +#include +#include "solvespace.h" + +namespace SolveSpace { + +//----------------------------------------------------------------------------- +// Resource loading functions +//----------------------------------------------------------------------------- + +std::string LoadString(const std::string &name) { + size_t size; + const void *data = Platform::LoadResource(name, &size); + std::string result(static_cast(data), size); + + // When editing resources under Windows, Windows newlines may sneak in. + // Any files with them won't be merged, but ignoring them during development + // helps external contributors. + result.erase(std::remove(result.begin(), result.end(), '\r'), + result.end()); + + return result; +} + +std::string LoadStringFromGzip(const std::string &name) { + size_t deflatedSize; + const void *data = Platform::LoadResource(name, &deflatedSize); + + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + const int windowBits = /*maximum window*/ 15 + /*decode gzip header*/16; + ssassert(inflateInit2(&stream, windowBits) == Z_OK, "Cannot start inflation"); + + // Extract length mod 2**32 from the gzip trailer. + std::string result; + ssassert(deflatedSize >= 4, "Resource too small to have gzip trailer"); + + // *(uint32_t *) may perform an unaligned access, so do a memcpy. + uint32_t inflatedSize; + memcpy(&inflatedSize, (uint8_t *)((uintptr_t)data + deflatedSize - 4), sizeof(uint32_t)); + result.resize(inflatedSize); + + stream.next_in = (Bytef *)data; + stream.avail_in = (uInt)deflatedSize; + stream.next_out = (Bytef *)&result[0]; + stream.avail_out = (uInt)result.length(); + ssassert(inflate(&stream, Z_NO_FLUSH) == Z_STREAM_END, "Cannot inflate resource"); + ssassert(stream.avail_out == 0, "Inflated resource larger than what trailer indicates"); + + inflateEnd(&stream); + + return result; +} + +std::shared_ptr LoadPng(const std::string &name) { + size_t size; + const void *data = Platform::LoadResource(name, &size); + + std::shared_ptr pixmap = Pixmap::FromPng(static_cast(data), size); + ssassert(pixmap != nullptr, "Cannot load pixmap"); + + return pixmap; +} + +//----------------------------------------------------------------------------- +// Pixmap manipulation +//----------------------------------------------------------------------------- + +size_t Pixmap::GetBytesPerPixel() const { + switch(format) { + case Format::RGBA: return 4; + case Format::BGRA: return 4; + case Format::RGB: return 3; + case Format::BGR: return 3; + case Format::A: return 1; + } + ssassert(false, "Unexpected pixmap format"); +} + +RgbaColor Pixmap::GetPixel(size_t x, size_t y) const { + const uint8_t *pixel = &data[y * stride + x * GetBytesPerPixel()]; + + switch(format) { + case Format::RGBA: + return RgbaColor::From(pixel[0], pixel[1], pixel[2], pixel[3]); + + case Format::RGB: + return RgbaColor::From(pixel[0], pixel[1], pixel[2], 255); + + case Format::BGRA: + return RgbaColor::From(pixel[2], pixel[1], pixel[0], pixel[3]); + + case Format::BGR: + return RgbaColor::From(pixel[2], pixel[1], pixel[0], 255); + + case Format::A: + return RgbaColor::From( 255, 255, 255, pixel[0]); + } + ssassert(false, "Unexpected resource format"); +} + +void Pixmap::SetPixel(size_t x, size_t y, RgbaColor color) { + uint8_t *pixel = &data[y * stride + x * GetBytesPerPixel()]; + + switch(format) { + case Format::RGBA: + pixel[0] = color.red; + pixel[1] = color.green; + pixel[2] = color.blue; + pixel[3] = color.alpha; + break; + + case Format::RGB: + pixel[0] = color.red; + pixel[1] = color.green; + pixel[2] = color.blue; + break; + + case Format::BGRA: + pixel[0] = color.blue; + pixel[1] = color.green; + pixel[2] = color.red; + pixel[3] = color.alpha; + break; + + case Format::BGR: + pixel[0] = color.blue; + pixel[1] = color.green; + pixel[2] = color.red; + break; + + case Format::A: + pixel[0] = color.alpha; + break; + } +} + +void Pixmap::ConvertTo(Format newFormat) { + switch(format) { + case Format::RGBA: + ssassert(newFormat == Format::BGRA, "Unexpected target format"); + break; + + case Format::BGRA: + ssassert(newFormat == Format::RGBA, "Unexpected target format"); + break; + + case Format::RGB: + ssassert(newFormat == Format::BGR, "Unexpected target format"); + break; + + case Format::BGR: + ssassert(newFormat == Format::RGB, "Unexpected target format"); + break; + + case Format::A: + ssassert(false, "Unexpected target format"); + } + + size_t bpp = GetBytesPerPixel(); + for(size_t j = 0; j != height; j++) { + uint8_t *row = &data[j * stride]; + for(size_t i = 0; i != width * bpp; i += bpp) { + // This handles both RGB<>BGR and RGBA<>BGRA. + std::swap(row[i], row[i + 2]); + } + } + + format = newFormat; +} + +static std::shared_ptr ReadPngIntoPixmap(png_struct *png_ptr, png_info *info_ptr, + bool flip) { + png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_GRAY_TO_RGB, NULL); + + std::shared_ptr pixmap = std::make_shared(); + pixmap->width = png_get_image_width(png_ptr, info_ptr); + pixmap->height = png_get_image_height(png_ptr, info_ptr); + if((png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_ALPHA) != 0) { + pixmap->format = Pixmap::Format::RGBA; + } else { + pixmap->format = Pixmap::Format::RGB; + } + + size_t stride = pixmap->width * pixmap->GetBytesPerPixel(); + if(stride % 4 != 0) stride += 4 - stride % 4; + pixmap->stride = stride; + + pixmap->data = std::vector(pixmap->stride * pixmap->height); + uint8_t **rows = png_get_rows(png_ptr, info_ptr); + for(size_t y = 0; y < pixmap->height; y++) { + uint8_t *srcRow = flip ? rows[pixmap->height - y - 1] : rows[y]; + memcpy(&pixmap->data[pixmap->stride * y], srcRow, + pixmap->width * pixmap->GetBytesPerPixel()); + } + + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return pixmap; +} + +std::shared_ptr Pixmap::FromPng(const uint8_t *data, size_t size, bool flip) { + struct Slice { const uint8_t *data; size_t size; }; + Slice dataSlice = { data, size }; + png_struct *png_ptr = NULL; + png_info *info_ptr = NULL; + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(!png_ptr) goto exit; + info_ptr = png_create_info_struct(png_ptr); + if(!info_ptr) goto exit; + + if(setjmp(png_jmpbuf(png_ptr))) goto exit; + + png_set_read_fn(png_ptr, &dataSlice, + [](png_struct *png_ptr, uint8_t *data, size_t size) { + Slice *dataSlice = (Slice *)png_get_io_ptr(png_ptr); + if(size <= dataSlice->size) { + memcpy(data, dataSlice->data, size); + dataSlice->data += size; + dataSlice->size -= size; + } else { + png_error(png_ptr, "EOF"); + } + }); + + return ReadPngIntoPixmap(png_ptr, info_ptr, flip); + +exit: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return nullptr; +} + +std::shared_ptr Pixmap::ReadPng(FILE *f, bool flip) { + png_struct *png_ptr = NULL; + png_info *info_ptr = NULL; + + uint8_t header[8]; + if(fread(header, 1, sizeof(header), f) != sizeof(header)) goto exit; + if(png_sig_cmp(header, 0, sizeof(header))) goto exit; + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(!png_ptr) goto exit; + info_ptr = png_create_info_struct(png_ptr); + if(!info_ptr) goto exit; + + if(setjmp(png_jmpbuf(png_ptr))) goto exit; + + png_init_io(png_ptr, f); + png_set_sig_bytes(png_ptr, sizeof(header)); + + return ReadPngIntoPixmap(png_ptr, info_ptr, flip); + +exit: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return nullptr; +} + +std::shared_ptr Pixmap::ReadPng(const Platform::Path &filename, bool flip) { + FILE *f = OpenFile(filename, "rb"); + if(!f) return NULL; + std::shared_ptr pixmap = ReadPng(f, flip); + fclose(f); + return pixmap; +} + +bool Pixmap::WritePng(FILE *f, bool flip) { + int colorType = 0; + bool bgr = false; + switch(format) { + case Format::RGBA: colorType = PNG_COLOR_TYPE_RGBA; bgr = false; break; + case Format::BGRA: colorType = PNG_COLOR_TYPE_RGBA; bgr = true; break; + case Format::RGB: colorType = PNG_COLOR_TYPE_RGB; bgr = false; break; + case Format::BGR: colorType = PNG_COLOR_TYPE_RGB; bgr = true; break; + case Format::A: colorType = PNG_COLOR_TYPE_GRAY; bgr = false; break; + } + + std::vector rows; + for(size_t y = 0; y < height; y++) { + if(flip) { + rows.push_back(&data[stride * (height - y - 1)]); + } else { + rows.push_back(&data[stride * y]); + } + } + + png_struct *png_ptr = NULL; + png_info *info_ptr = NULL; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(!png_ptr) goto exit; + info_ptr = png_create_info_struct(png_ptr); + if(!info_ptr) goto exit; + + if(setjmp(png_jmpbuf(png_ptr))) goto exit; + + png_init_io(png_ptr, f); + png_set_IHDR(png_ptr, info_ptr, + (png_uint_32)width, (png_uint_32)height, 8, colorType, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + if(bgr) png_set_bgr(png_ptr); + png_write_info(png_ptr, info_ptr); + png_write_image(png_ptr, &rows[0]); + png_write_end(png_ptr, info_ptr); + + png_destroy_write_struct(&png_ptr, &info_ptr); + return true; + +exit: + png_destroy_write_struct(&png_ptr, &info_ptr); + return false; +} + +bool Pixmap::WritePng(const Platform::Path &filename, bool flip) { + FILE *f = OpenFile(filename, "wb"); + if(!f) return false; + bool success = WritePng(f, flip); + fclose(f); + return success; +} + +bool Pixmap::Equals(const Pixmap &other) const { + if(format != other.format || width != other.width || height != other.height) { + return false; + } + + size_t rowLength = width * GetBytesPerPixel(); + for(size_t y = 0; y < height; y++) { + if(memcmp(&data[y * stride], &other.data[y * other.stride], rowLength)) { + return false; + } + } + + return true; +} + +std::shared_ptr Pixmap::Create(Format format, size_t width, size_t height) { + std::shared_ptr pixmap = std::make_shared(); + pixmap->format = format; + pixmap->width = width; + pixmap->height = height; + // Align to fulfill OpenGL texture requirements. + size_t stride = pixmap->width * pixmap->GetBytesPerPixel(); + if(stride % 4 != 0) stride += 4 - stride % 4; + pixmap->stride = stride; + pixmap->data = std::vector(pixmap->stride * pixmap->height); + return pixmap; +} + +std::shared_ptr Pixmap::Copy() { + std::shared_ptr pixmap = std::make_shared(); + pixmap->format = format; + pixmap->width = width; + pixmap->height = height; + pixmap->stride = stride; + pixmap->data = data; + return pixmap; +} + +//----------------------------------------------------------------------------- +// ASCII sequence parsing +//----------------------------------------------------------------------------- + +class ASCIIReader { +public: + std::string::const_iterator pos, end; + + static ASCIIReader From(const std::string &str) { + return ASCIIReader({ str.cbegin(), str.cend() }); + } + + bool AtEnd() const { + return pos == end; + } + + bool SkipSpace() { + bool skipped = false; + while(!AtEnd()) { + char c = *pos; + if(!(c == ' ' || c == '\t' || c == '\n')) break; + skipped = true; + pos++; + } + return skipped; + } + + char PeekChar() { + ssassert(!AtEnd(), "Unexpected EOF"); + return *pos; + } + + char ReadChar() { + ssassert(!AtEnd(), "Unexpected EOF"); + return *pos++; + } + + bool TryChar(char c) { + if(AtEnd()) { + return false; + } else if(*pos == c) { + pos++; + return true; + } else { + return false; + } + } + + void ExpectChar(char c) { + if(!TryChar(c)) { + dbp("Expecting character '%c'", c); + ssassert(false, "Unexpected character"); + } + } + + bool TryString(const std::string &s) { + if((size_t)(end - pos) >= s.size() && std::string(pos, pos + s.size()) == s) { + pos += s.size(); + return true; + } else { + return false; + } + } + + void ExpectString(const std::string &s) { + if(!TryString(s)) { + dbp("Expecting string '%s'", s.c_str()); + ssassert(false, "Unexpected string"); + } + } + + size_t CountUntilEol() const { + return std::find(pos, end, '\n') - pos; + } + + void SkipUntilEol() { + pos = std::find(pos, end, '\n'); + } + + std::string ReadUntilEol() { + auto eol = std::find(pos, end, '\n'); + std::string result(pos, eol); + if(eol != end) { + pos = eol + 1; + } else { + pos = end; + } + return result; + } + + uint8_t Read4HexBits() { + char c = ReadChar(); + if(c >= '0' && c <= '9') { + return c - '0'; + } else if(c >= 'a' && c <= 'f') { + return 10 + (c - 'a'); + } else if(c >= 'A' && c <= 'F') { + return 10 + (c - 'A'); + } else ssassert(false, "Unexpected hex digit"); + } + + uint8_t Read8HexBits() { + uint8_t h = Read4HexBits(), + l = Read4HexBits(); + return (h << 4) + l; + } + + uint16_t Read16HexBits() { + uint16_t h = Read8HexBits(), + l = Read8HexBits(); + return (h << 8) + l; + } + + long ReadIntegerDecimal(int base = 10) { + char *endptr; + long l = strtol(&*pos, &endptr, base); + ssassert(&*pos != endptr, "Cannot read an integer number"); + pos += endptr - &*pos; + return l; + } + + double ReadFloatDecimal() { + char *endptr; + double d = strtod(&*pos, &endptr); + ssassert(&*pos != endptr, "Cannot read a floating-point number"); + pos += endptr - &*pos; + return d; + } + + bool TryRegex(const std::regex &re, std::smatch *m) { + if(std::regex_search(pos, end, *m, re, std::regex_constants::match_continuous)) { + pos += m->length(); + return true; + } else { + return false; + } + } + + void ExpectRegex(const std::regex &re, std::smatch *m) { + ssassert(TryRegex(re, m), "Unmatched regex"); + } +}; + +//----------------------------------------------------------------------------- +// Bitmap font manipulation +//----------------------------------------------------------------------------- + +static uint8_t *BitmapFontTextureRow(std::shared_ptr texture, + uint16_t position, size_t y) { + // position = 0; + size_t col = position % (texture->width / 16), + row = position / (texture->width / 16); + return &texture->data[texture->stride * (16 * row + y) + 16 * col]; +} + +BitmapFont BitmapFont::From(std::string &&unifontData) { + BitmapFont font = {}; + font.unifontData = std::move(unifontData); + font.texture = Pixmap::Create(Pixmap::Format::A, 1024, 1024); + + return font; +} + +void BitmapFont::AddGlyph(char32_t codepoint, std::shared_ptr pixmap) { + ssassert((pixmap->width == 8 || pixmap->width == 16) && pixmap->height == 16, + "Unexpected pixmap dimensions"); + ssassert(pixmap->format == Pixmap::Format::RGB, + "Unexpected pixmap format"); + ssassert(glyphs.find(codepoint) == glyphs.end(), + "Glyph with this codepoint already exists"); + ssassert(nextPosition != 0xffff, + "Too many glyphs for current texture size"); + + BitmapFont::Glyph glyph = {}; + glyph.advanceCells = (uint8_t)(pixmap->width / 8); + glyph.position = nextPosition++; + glyphs.emplace(codepoint, glyph); + + for(size_t y = 0; y < pixmap->height; y++) { + uint8_t *row = BitmapFontTextureRow(texture, glyph.position, y); + for(size_t x = 0; x < pixmap->width; x++) { + if((pixmap->GetPixel(x, y).ToPackedInt() & 0xffffff) != 0) { + row[x] = 255; + } + } + } +} + +const BitmapFont::Glyph &BitmapFont::GetGlyph(char32_t codepoint) { + auto it = glyphs.find(codepoint); + if(it != glyphs.end()) { + return (*it).second; + } + + ssassert(nextPosition != 0xffff, + "Too many glyphs for current texture size"); + + // Find the hex representation in the (sorted) Unifont file. + auto first = unifontData.cbegin(), + last = unifontData.cend(); + while(first <= last) { + auto mid = first + (last - first) / 2; + while(mid != unifontData.cbegin()) { + if(*mid == '\n') { + mid++; + break; + } + mid--; + } + + ASCIIReader reader = { mid, unifontData.cend() }; + if(reader.AtEnd()) break; + + // Read the codepoint. + char32_t foundCodepoint = reader.Read16HexBits(); + reader.ExpectChar(':'); + + if(foundCodepoint > codepoint) { + last = mid - 1; + continue; // and first stays the same + } + if(foundCodepoint < codepoint) { + first = mid + 1; + while(first != unifontData.cend()) { + if(*first == '\n') break; + first++; + } + continue; // and last stays the same + } + + // Found the codepoint. + Glyph glyph = {}; + glyph.position = nextPosition++; + + // Read glyph bits. + unsigned short glyphBits[16]; + size_t glyphLength = reader.CountUntilEol(); + if(glyphLength == 4 * 16) { + glyph.advanceCells = 2; + for(size_t i = 0; i < 16; i++) { + glyphBits[i] = reader.Read16HexBits(); + } + } else if(glyphLength == 2 * 16) { + glyph.advanceCells = 1; + for(size_t i = 0; i < 16; i++) { + glyphBits[i] = (uint16_t)reader.Read8HexBits() << 8; + } + } else ssassert(false, "Unexpected glyph bitmap length"); + + // Fill in the texture (one texture byte per glyph bit). + for(size_t y = 0; y < 16; y++) { + uint8_t *row = BitmapFontTextureRow(texture, glyph.position, y); + for(size_t x = 0; x < 16; x++) { + if(glyphBits[y] & (1 << (15 - x))) { + row[x] = 255; + } + } + } + + it = glyphs.emplace(codepoint, glyph).first; + + textureUpdated = true; + return (*it).second; + } + + // Glyph doesn't exist; return replacement glyph instead. + ssassert(codepoint != 0xfffd, "Cannot parse replacement glyph"); + return GetGlyph(0xfffd); +} + +void BitmapFont::LocateGlyph(char32_t codepoint, + double *s0, double *t0, double *s1, double *t1, + size_t *w, size_t *h) { + const Glyph &glyph = GetGlyph(codepoint); + *w = glyph.advanceCells * 8; + *h = 16; + *s0 = (16.0 * (glyph.position % (texture->width / 16))) / texture->width; + *s1 = *s0 + (double)(*w) / texture->width; + *t0 = (16.0 * (glyph.position / (texture->width / 16))) / texture->height; + *t1 = *t0 + (double)(*h) / texture->height; +} + +size_t BitmapFont::GetWidth(char32_t codepoint) { + if(codepoint >= 0xe000 && codepoint <= 0xefff) { + // These are special-cased because checkboxes predate support for 2 cell wide + // characters; and so all Printf() calls pad them with spaces. + return 1; + } + + return GetGlyph(codepoint).advanceCells; +} + +size_t BitmapFont::GetWidth(const std::string &str) { + size_t width = 0; + for(char32_t codepoint : ReadUTF8(str)) { + width += GetWidth(codepoint); + } + return width; +} + +BitmapFont BitmapFont::Create() { + BitmapFont Font = BitmapFont::From(LoadStringFromGzip("fonts/unifont.hex.gz")); + // Unifont doesn't have a glyph for U+0020. + Font.AddGlyph(0x0020, Pixmap::Create(Pixmap::Format::RGB, 8, 16)); + Font.AddGlyph(0xE000, LoadPng("fonts/private/0-check-false.png")); + Font.AddGlyph(0xE001, LoadPng("fonts/private/1-check-true.png")); + Font.AddGlyph(0xE002, LoadPng("fonts/private/2-radio-false.png")); + Font.AddGlyph(0xE003, LoadPng("fonts/private/3-radio-true.png")); + Font.AddGlyph(0xE004, LoadPng("fonts/private/4-stipple-dot.png")); + Font.AddGlyph(0xE005, LoadPng("fonts/private/5-stipple-dash-long.png")); + Font.AddGlyph(0xE006, LoadPng("fonts/private/6-stipple-dash.png")); + Font.AddGlyph(0xE007, LoadPng("fonts/private/7-stipple-zigzag.png")); + return Font; +} + +//----------------------------------------------------------------------------- +// Vector font manipulation +//----------------------------------------------------------------------------- + +const static int ARC_POINTS = 8; +static void MakePwlArc(VectorFont::Contour *contour, bool isReversed, + const Point2d &cp, double radius, double a1, double a2) { + if(radius < LENGTH_EPS) return; + + double aSign = 1.0; + if(isReversed) { + if(a1 <= a2 + LENGTH_EPS) a1 += 2.0 * M_PI; + aSign = -1.0; + } else { + if(a2 <= a1 + LENGTH_EPS) a2 += 2.0 * M_PI; + } + + double aStep = aSign * fabs(a2 - a1) / (double)ARC_POINTS; + for(int i = 0; i <= ARC_POINTS; i++) { + contour->points.emplace_back(cp.Plus(Point2d::FromPolar(radius, a1 + aStep * i))); + } +} + +static void MakePwlBulge(VectorFont::Contour *contour, const Point2d &v, double bulge) { + bool reversed = bulge < 0.0; + double alpha = atan(bulge) * 4.0; + const Point2d &point = contour->points.back(); + + Point2d middle = point.Plus(v).ScaledBy(0.5); + double dist = point.DistanceTo(v) / 2.0; + double angle = point.AngleTo(v); + + // alpha can't be 0.0 at this point + double radius = fabs(dist / sin(alpha / 2.0)); + double wu = fabs(radius*radius - dist*dist); + double h = sqrt(wu); + + if(bulge > 0.0) { + angle += M_PI_2; + } else { + angle -= M_PI_2; + } + + if(fabs(alpha) > M_PI) { + h = -h; + } + + Point2d center = Point2d::FromPolar(h, angle).Plus(middle); + double a1 = center.AngleTo(point); + double a2 = center.AngleTo(v); + MakePwlArc(contour, reversed, center, radius, a1, a2); +} + +static void GetGlyphBBox(const VectorFont::Glyph &glyph, + double *rminx, double *rmaxx, double *rminy, double *rmaxy) { + double minx = 0.0, maxx = 0.0, miny = 0.0, maxy = 0.0; + if(!glyph.contours.empty()) { + const Point2d &start = glyph.contours[0].points[0]; + minx = maxx = start.x; + miny = maxy = start.y; + for(const VectorFont::Contour &c : glyph.contours) { + for(const Point2d &p : c.points) { + maxx = std::max(maxx, p.x); + minx = std::min(minx, p.x); + maxy = std::max(maxy, p.y); + miny = std::min(miny, p.y); + } + } + } + + if(rminx) *rminx = minx; + if(rmaxx) *rmaxx = maxx; + if(rminy) *rminy = miny; + if(rmaxy) *rmaxy = maxy; +} + +VectorFont VectorFont::From(std::string &&lffData) { + VectorFont font = {}; + font.lffData = std::move(lffData); + + ASCIIReader reader = ASCIIReader::From(font.lffData); + std::smatch m; + while(reader.TryRegex(std::regex("#\\s*(\\w+)\\s*:\\s*(.+?)\n"), &m)) { + std::string name = m.str(1), + value = m.str(2); + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + if(name == "letterspacing") { + font.rightSideBearing = std::stod(value); + } else if(name == "wordspacing") { + Glyph space = {}; + space.advanceWidth = std::stod(value); + font.glyphs.emplace(' ', std::move(space)); + } + } + + GetGlyphBBox(font.GetGlyph('A'), nullptr, nullptr, nullptr, &font.capHeight); + GetGlyphBBox(font.GetGlyph('h'), nullptr, nullptr, nullptr, &font.ascender); + GetGlyphBBox(font.GetGlyph('p'), nullptr, nullptr, &font.descender, nullptr); + + ssassert(!font.IsEmpty(), "Expected to load a font"); + return font; +} + +const VectorFont::Glyph &VectorFont::GetGlyph(char32_t codepoint) { + auto it = glyphs.find(codepoint); + if(it != glyphs.end()) { + return (*it).second; + } + + auto firstGlyph = std::find(lffData.cbegin(), lffData.cend(), '['); + ssassert(firstGlyph != lffData.cend(), "Vector font contains no glyphs"); + + // Find the serialized representation in the (sorted) lff file. + auto first = firstGlyph, + last = lffData.cend(); + while(first <= last) { + auto mid = first + (last - first) / 2; + while(mid > first) { + if(*mid == '[' && *(mid - 1) == '\n') break; + mid--; + } + + ASCIIReader reader = { mid, lffData.cend() }; + if(reader.AtEnd()) break; + + // Read the codepoint. + reader.ExpectChar('['); + char32_t foundCodepoint = reader.Read16HexBits(); + reader.ExpectChar(']'); + reader.SkipUntilEol(); + + if(foundCodepoint > codepoint) { + last = mid - 1; + continue; // and first stays the same + } + if(foundCodepoint < codepoint) { + first = mid + 1; + while(first != lffData.cend()) { + if(*first == '[' && *(first - 1) == '\n') break; + first++; + } + continue; // and last stays the same + } + + // Found the codepoint. + VectorFont::Glyph glyph = {}; + + // Read glyph contours. + while(!reader.AtEnd()) { + if(reader.TryChar('\n')) { + // Skip. + } else if(reader.TryChar('[')) { + // End of glyph. + if(glyph.contours.back().points.empty()) { + // Remove an useless empty contour, if any. + glyph.contours.pop_back(); + } + break; + } else if(reader.TryChar('C')) { + // Another character is referenced in this one. + char32_t baseCodepoint = reader.Read16HexBits(); + const VectorFont::Glyph &baseGlyph = GetGlyph(baseCodepoint); + std::copy(baseGlyph.contours.begin(), baseGlyph.contours.end(), + std::back_inserter(glyph.contours)); + } else { + Contour contour; + do { + Point2d p; + p.x = reader.ReadFloatDecimal(); + reader.ExpectChar(','); + p.y = reader.ReadFloatDecimal(); + + if(reader.TryChar(',')) { + // Point with a bulge. + reader.ExpectChar('A'); + double bulge = reader.ReadFloatDecimal(); + MakePwlBulge(&contour, p, bulge); + } else { + // Just a point. + contour.points.emplace_back(std::move(p)); + } + } while(reader.TryChar(';')); + reader.ExpectChar('\n'); + glyph.contours.emplace_back(std::move(contour)); + } + } + + // Calculate metrics. + GetGlyphBBox(glyph, &glyph.leftSideBearing, &glyph.boundingWidth, nullptr, nullptr); + glyph.advanceWidth = glyph.leftSideBearing + glyph.boundingWidth + rightSideBearing; + + it = glyphs.emplace(codepoint, std::move(glyph)).first; + return (*it).second; + } + + // Glyph doesn't exist; return replacement glyph instead. + ssassert(codepoint != 0xfffd, "Cannot parse replacement glyph"); + return GetGlyph(0xfffd); +} + +VectorFont *VectorFont::Builtin() { + static VectorFont Font; + if(Font.IsEmpty()) { + Font = VectorFont::From(LoadStringFromGzip("fonts/unicode.lff.gz")); + } + return &Font; +} + +double VectorFont::GetCapHeight(double forCapHeight) const { + ssassert(!IsEmpty(), "Expected a loaded font"); + + return forCapHeight; +} + +double VectorFont::GetHeight(double forCapHeight) const { + ssassert(!IsEmpty(), "Expected a loaded font"); + + return (ascender - descender) * (forCapHeight / capHeight); +} + +double VectorFont::GetWidth(double forCapHeight, const std::string &str) { + ssassert(!IsEmpty(), "Expected a loaded font"); + + double width = 0; + for(char32_t codepoint : ReadUTF8(str)) { + width += GetGlyph(codepoint).advanceWidth; + } + width -= rightSideBearing; + return width * (forCapHeight / capHeight); +} + +Vector VectorFont::GetExtents(double forCapHeight, const std::string &str) { + Vector ex = {}; + ex.x = GetWidth(forCapHeight, str); + ex.y = GetHeight(forCapHeight); + return ex; +} + +void VectorFont::Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str, + const std::function &traceEdge) { + ssassert(!IsEmpty(), "Expected a loaded font"); + + double scale = (forCapHeight / capHeight); + u = u.ScaledBy(scale); + v = v.ScaledBy(scale); + + for(char32_t codepoint : ReadUTF8(str)) { + const Glyph &glyph = GetGlyph(codepoint); + + for(const VectorFont::Contour &contour : glyph.contours) { + Vector prevp; + bool penUp = true; + for(const Point2d &pt : contour.points) { + Vector p = o.Plus(u.ScaledBy(pt.x)) + .Plus(v.ScaledBy(pt.y)); + if(!penUp) traceEdge(prevp, p); + prevp = p; + penUp = false; + } + } + + o = o.Plus(u.ScaledBy(glyph.advanceWidth)); + } +} + +void VectorFont::Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str, + const std::function &traceEdge, const Camera &camera) { + ssassert(!IsEmpty(), "Expected a loaded font"); + + // Perform grid-fitting only when the text is parallel to the view plane. + if(camera.gridFit && !(u.WithMagnitude(1).Equals(camera.projRight) && + v.WithMagnitude(1).Equals(camera.projUp))) { + return Trace(forCapHeight, o, u, v, str, traceEdge); + } + + double scale = forCapHeight / capHeight; + u = u.ScaledBy(scale); + v = v.ScaledBy(scale); + + for(char32_t codepoint : ReadUTF8(str)) { + const Glyph &glyph = GetGlyph(codepoint); + double actualWidth = std::max(1.0, glyph.boundingWidth); + + // Align (o+lsb), (o+lsb+u) and (o+lsb+v) to pixel grid. + Vector ao = o.Plus(u.ScaledBy(glyph.leftSideBearing)); + Vector au = ao.Plus(u.ScaledBy(actualWidth)); + Vector av = ao.Plus(v.ScaledBy(capHeight)); + + ao = camera.AlignToPixelGrid(ao); + au = camera.AlignToPixelGrid(au); + av = camera.AlignToPixelGrid(av); + + au = au.Minus(ao).ScaledBy(1.0 / actualWidth); + av = av.Minus(ao).ScaledBy(1.0 / capHeight); + + for(const VectorFont::Contour &contour : glyph.contours) { + Vector prevp; + bool penUp = true; + for(const Point2d &pt : contour.points) { + Vector p = ao.Plus(au.ScaledBy(pt.x - glyph.leftSideBearing)) + .Plus(av.ScaledBy(pt.y)); + if(!penUp) traceEdge(prevp, p); + prevp = p; + penUp = false; + } + } + + o = o.Plus(u.ScaledBy(glyph.advanceWidth)); + } +} + +//----------------------------------------------------------------------------- +// Gettext plural expression evaluation +//----------------------------------------------------------------------------- + +class PluralExpr { +public: + class Token { + public: + enum class Type { + END, + VALUE, + BINARY_OP, + QUERY, + COLON, + PAREN_LEFT, + PAREN_RIGHT, + }; + + // Only valid for type == BINARY_OP. + enum class Op { + NONE, + // comparison + EQ, // == + NEQ, // != + LT, // < + GT, // > + LE, // <= + GE, // >= + // logical + AND, // && + OR, // || + // arithmetic + MOD, // % + }; + + Type type; + Op op; + unsigned value; + + int Precedence(); + }; + + ASCIIReader reader; + std::vector stack; + unsigned value; + + Token Lex(); + + Token PopToken(); + void Reduce(); + void Eval(); + + static unsigned Eval(const std::string &s, unsigned n); +}; + +int PluralExpr::Token::Precedence() { + switch(type) { + case Type::BINARY_OP: + switch(op) { + case Op::MOD: + return 7; + + case Op::LT: + case Op::GT: + case Op::LE: + case Op::GE: + return 6; + + case Op::EQ: + case Op::NEQ: + return 5; + + case Op::AND: + return 4; + + case Op::OR: + return 3; + + case Op::NONE: + ; + } + ssassert(false, "Unexpected operator"); + + case Type::QUERY: + case Type::COLON: + return 1; + + case Type::VALUE: + return 0; + + default: + ssassert(false, "Unexpected token op"); + } +} + +PluralExpr::Token PluralExpr::Lex() { + Token t = {}; + + reader.SkipSpace(); + + char c = reader.PeekChar(); + if(c >= '0' && c <= '9') { + t.type = Token::Type::VALUE; + t.value = reader.ReadIntegerDecimal(); + } else if(reader.TryChar('n')) { + t.type = Token::Type::VALUE; + t.value = value; + } else if(reader.TryChar('%')) { + t.type = Token::Type::BINARY_OP; + t.op = Token::Op::MOD; + } else if(reader.TryChar('<')) { + t.type = Token::Type::BINARY_OP; + if(reader.TryChar('=')) { + t.op = Token::Op::LE; + } else { + t.op = Token::Op::LT; + } + } else if(reader.TryChar('>')) { + t.type = Token::Type::BINARY_OP; + if(reader.TryChar('=')) { + t.op = Token::Op::GE; + } else { + t.op = Token::Op::GT; + } + } else if(reader.TryChar('!')) { + reader.ExpectChar('='); + t.type = Token::Type::BINARY_OP; + t.op = Token::Op::NEQ; + } else if(reader.TryChar('=')) { + reader.ExpectChar('='); + t.type = Token::Type::BINARY_OP; + t.op = Token::Op::EQ; + } else if(reader.TryChar('&')) { + reader.ExpectChar('&'); + t.type = Token::Type::BINARY_OP; + t.op = Token::Op::AND; + } else if(reader.TryChar('|')) { + reader.ExpectChar('|'); + t.type = Token::Type::BINARY_OP; + t.op = Token::Op::OR; + } else if(reader.TryChar('?')) { + t.type = Token::Type::QUERY; + } else if(reader.TryChar(':')) { + t.type = Token::Type::COLON; + } else if(reader.TryChar('(')) { + t.type = Token::Type::PAREN_LEFT; + } else if(reader.TryChar(')')) { + t.type = Token::Type::PAREN_RIGHT; + } else if(reader.AtEnd()) { + t.type = Token::Type::END; + } else { + ssassert(false, "Unexpected character"); + } + + return t; +} + +PluralExpr::Token PluralExpr::PopToken() { + ssassert(!stack.empty(), "Expected a non-empty stack"); + Token t = stack.back(); + stack.pop_back(); + return t; +} + +void PluralExpr::Reduce() { + Token r; + r.type = Token::Type::VALUE; + + Token a = PopToken(); + ssassert(a.type == Token::Type::VALUE, "Expected 1st operand to be a value"); + + Token op = PopToken(); + switch(op.type) { + case Token::Type::BINARY_OP: { + Token b = PopToken(); + ssassert(b.type == Token::Type::VALUE, "Expected 2nd operand to be a value"); + + switch(op.op) { + case Token::Op::EQ: + r.value = (a.value == b.value ? 1 : 0); + break; + case Token::Op::NEQ: + r.value = (a.value != b.value ? 1 : 0); + break; + case Token::Op::LT: + r.value = (b.value < a.value ? 1 : 0); + break; + case Token::Op::GT: + r.value = (b.value > a.value ? 1 : 0); + break; + case Token::Op::LE: + r.value = (b.value <= a.value ? 1 : 0); + break; + case Token::Op::GE: + r.value = (b.value >= a.value ? 1 : 0); + break; + case Token::Op::AND: + r.value = a.value && b.value; + break; + case Token::Op::OR: + r.value = a.value || b.value; + break; + case Token::Op::MOD: + r.value = b.value % a.value; + break; + case Token::Op::NONE: + ssassert(false, "Unexpected operator"); + } + break; + } + + case Token::Type::COLON: { + Token b = PopToken(); + ssassert(PopToken().type == Token::Type::QUERY, "Expected ?"); + Token c = PopToken(); + r.value = c.value ? b.value : a.value; + break; + } + + default: + ssassert(false, "Unexpected operator type"); + } + + stack.push_back(r); +} + +void PluralExpr::Eval() { + while(true) { + Token t = Lex(); + switch(t.type) { + case Token::Type::END: + case Token::Type::PAREN_RIGHT: + while(stack.size() > 1 && + stack.end()[-2].type != Token::Type::PAREN_LEFT) { + Reduce(); + } + if(t.type == Token::Type::PAREN_RIGHT) { + ssassert(stack.size() > 1, "Expected ("); + stack.push_back(t); + } + return; + + case Token::Type::PAREN_LEFT: + stack.push_back(t); + Eval(); + if(stack.back().type != Token::Type::PAREN_RIGHT) { + ssassert(false, "Expected )"); + } + stack.pop_back(); + stack.erase(stack.end() - 2); + break; + + case Token::Type::VALUE: + stack.push_back(t); + break; + + case Token::Type::BINARY_OP: + case Token::Type::QUERY: + case Token::Type::COLON: + while(stack.size() > 1 && + stack.end()[-2].type != Token::Type::PAREN_LEFT && + t.Precedence() < stack.end()[-2].Precedence()) { + Reduce(); + } + stack.push_back(t); + break; + } + } +} + +unsigned PluralExpr::Eval(const std::string &s, unsigned n) { + PluralExpr expr = {}; + expr.reader = ASCIIReader::From(s); + expr.value = n; + expr.Eval(); + + Token t = expr.PopToken(); + ssassert(t.type == Token::Type::VALUE, "Expected a value"); + return t.value; +} + +//----------------------------------------------------------------------------- +// Gettext message keys +//----------------------------------------------------------------------------- + +class TranslationKey { +public: + bool hasContext; + std::string context; + std::string ident; +}; + +struct TranslationKeyLess { + bool operator()(const TranslationKey &a, const TranslationKey &b) const { + return a.hasContext < b.hasContext || + (a.hasContext == b.hasContext && a.context < b.context) || + (a.hasContext == b.hasContext && a.context == b.context && a.ident < b.ident); + } +}; + +//----------------------------------------------------------------------------- +// Gettext .po file parsing +//----------------------------------------------------------------------------- + +class GettextParser { +public: + ASCIIReader reader; + + unsigned pluralCount; + std::string pluralExpr; + + std::map, TranslationKeyLess> messages; + + void SkipSpace(); + std::string ReadString(); + void ParseHeader(const std::string &header); + void Parse(); +}; + +void GettextParser::SkipSpace() { + while(!reader.AtEnd()) { + if(reader.TryChar('#')) { + reader.SkipUntilEol(); + } else if(!reader.SkipSpace()) { + break; + } + } +} + +std::string GettextParser::ReadString() { + SkipSpace(); + reader.ExpectChar('"'); + + std::string result; + while(true) { + if(reader.AtEnd()) { + ssassert(false, "Unexpected EOF within a string"); + } else if(reader.TryChar('\"')) { + SkipSpace(); + if(!reader.TryChar('\"')) { + break; + } + } else if(reader.TryChar('\\')) { + if(reader.TryChar('\\')) { + result += '\\'; + } else if(reader.TryChar('n')) { + result += '\n'; + } else if(reader.TryChar('t')) { + result += '\t'; + } else if(reader.TryChar('"')) { + result += '"'; + } else { + ssassert(false, "Unexpected escape sequence"); + } + } else { + result += reader.ReadChar(); + } + } + return result; +} + +void GettextParser::ParseHeader(const std::string &header) { + ASCIIReader reader = ASCIIReader::From(header); + while(!reader.AtEnd()) { + reader.SkipSpace(); + if(reader.TryString("Plural-Forms:")) { + reader.SkipSpace(); + reader.ExpectString("nplurals="); + reader.SkipSpace(); + pluralCount = reader.ReadIntegerDecimal(); + reader.SkipSpace(); + reader.ExpectString(";"); + reader.SkipSpace(); + reader.ExpectString("plural="); + pluralExpr = reader.ReadUntilEol(); + } else { + reader.SkipUntilEol(); + } + } +} + +void GettextParser::Parse() { + // Default to a single form, in case a header is missing. + pluralCount = 1; + pluralExpr = "0"; + + SkipSpace(); + while(!reader.AtEnd()) { + TranslationKey key = {}; + + if(reader.TryString("msgctxt")) { + key.hasContext = true; + key.context = ReadString(); + } + + reader.ExpectString("msgid"); + key.ident = ReadString(); + + if(reader.TryString("msgid_plural")) { + ReadString(); // we don't need it + } + + std::vector msgstrs; + while(reader.TryString("msgstr")) { + if(reader.TryChar('[')) { + unsigned index = reader.ReadIntegerDecimal(); + reader.ExpectChar(']'); + if(msgstrs.size() <= index) { + msgstrs.resize(index + 1); + } + msgstrs[index] = ReadString(); + } else { + msgstrs.emplace_back(ReadString()); + break; + } + } + + if(key.ident.empty()) { + ssassert(msgstrs.size() == 1, + "Expected exactly one header msgstr"); + ParseHeader(msgstrs[0]); + } else { + ssassert(msgstrs.size() == 1 || + msgstrs.size() == pluralCount, + "Expected msgstr count to match plural form count"); + messages.emplace(key, msgstrs); + } + } +} + +//----------------------------------------------------------------------------- +// Translation management +//----------------------------------------------------------------------------- + +class Translation { +public: + unsigned pluralCount; + std::string pluralExpr; + + std::map, TranslationKeyLess> messages; + + static Translation From(const std::string &poData); + + const std::string &Translate(const TranslationKey &key); + const std::string &TranslatePlural(const TranslationKey &key, unsigned n); +}; + +Translation Translation::From(const std::string &poData) { + GettextParser parser = {}; + parser.reader = ASCIIReader::From(poData); + parser.Parse(); + + Translation trans = {}; + trans.pluralCount = parser.pluralCount; + trans.pluralExpr = parser.pluralExpr; + trans.messages = parser.messages; + return trans; +} + +const std::string &Translation::Translate(const TranslationKey &key) { + auto it = messages.find(key); + if(it == messages.end()) { + dbp("Missing (absent) translation for %s'%s'", key.context.c_str(), key.ident.c_str()); + messages[key].emplace_back(key.ident); + it = messages.find(key); + } + if(it->second[0].empty()) { + dbp("Missing (empty) translation for %s'%s'", key.context.c_str(), key.ident.c_str()); + it->second[0] = key.ident; + } + if(it->second.size() != 1) { + dbp("Incorrect use of translated message %s'%s'", key.context.c_str(), key.ident.c_str()); + ssassert(false, "Using a message with a plural form without a number"); + } + return it->second[0]; +} + +const std::string &Translation::TranslatePlural(const TranslationKey &key, unsigned n) { + unsigned pluralForm = PluralExpr::Eval(pluralExpr, n); + + auto it = messages.find(key); + if(it == messages.end()) { + dbp("Missing (absent) translation for %s'%s'", key.context.c_str(), key.ident.c_str()); + for(unsigned i = 0; i < pluralCount; i++) { + messages[key].emplace_back(key.ident); + } + it = messages.find(key); + } + if(it->second[pluralForm].empty()) { + dbp("Missing (empty) translation for %s'%s'[%d]", + key.context.c_str(), key.ident.c_str(), pluralForm); + it->second[pluralForm] = key.ident; + } + return it->second[pluralForm]; +} + +//----------------------------------------------------------------------------- +// Locale management +//----------------------------------------------------------------------------- + +static std::set locales; +static std::map translations; +static Translation dummyTranslation; +static Translation *currentTranslation = &dummyTranslation; + +const std::set &Locales() { + if(!locales.empty()) return locales; + + std::string localeList = LoadString("locales.txt"); + ASCIIReader reader = ASCIIReader::From(localeList); + while(!reader.AtEnd()) { + reader.SkipSpace(); + if(reader.TryChar('#')) { + reader.SkipUntilEol(); + continue; + } + + std::smatch m; + reader.ExpectRegex(std::regex("([a-z]{2})-([A-Z]{2}),([0-9A-F]{4}),(.+?)\n"), &m); + Locale locale = {}; + locale.language = m.str(1); + locale.region = m.str(2); + locale.lcid = std::stoi(m.str(3), NULL, 16); + locale.displayName = m.str(4); + locales.emplace(locale); + } + return locales; +} + +template +bool SetLocale(Predicate pred) { + auto it = std::find_if(Locales().begin(), Locales().end(), pred); + if(it != locales.end()) { + std::string filename = "locales/" + it->language + "_" + it->region + ".po"; + translations[*it] = Translation::From(LoadString(filename)); + currentTranslation = &translations[*it]; + return true; + } else { + return false; + } +} + +bool SetLocale(const std::string &name) { + return SetLocale([&](const Locale &locale) { + if(name == locale.language + "-" + locale.region) { + return true; + } else if(name == locale.language + "_" + locale.region) { + return true; + } else if(name == locale.language) { + return true; + } else { + return false; + } + }); +} + +bool SetLocale(uint16_t lcid) { + return SetLocale([&](const Locale &locale) { + return locale.lcid == lcid; + }); +} + +const std::string &Translate(const char *msgid) { + TranslationKey key = {}; + key.ident = msgid; + return currentTranslation->Translate(key); +} + +const std::string &Translate(const char *msgctxt, const char *msgid) { + TranslationKey key = {}; + key.hasContext = true; + key.context = msgctxt; + key.ident = msgid; + return currentTranslation->Translate(key); +} + +const std::string &TranslatePlural(const char *msgid, unsigned n) { + TranslationKey key = {}; + key.ident = msgid; + return currentTranslation->TranslatePlural(key, n); +} + +const std::string &TranslatePlural(const char *msgctxt, const char *msgid, unsigned n) { + TranslationKey key = {}; + key.hasContext = true; + key.context = msgctxt; + key.ident = msgid; + return currentTranslation->TranslatePlural(key, n); +} + +} diff --git a/src/resource.h b/src/resource.h new file mode 100644 index 0000000..18c1e58 --- /dev/null +++ b/src/resource.h @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// Discovery and loading of our resources (icons, fonts, templates, etc). +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- + +#ifndef SOLVESPACE_RESOURCE_H +#define SOLVESPACE_RESOURCE_H + +class Camera; +class Point2d; +class Pixmap; +class Vector; +class RgbaColor; + +std::string LoadString(const std::string &name); +std::string LoadStringFromGzip(const std::string &name); +std::shared_ptr LoadPng(const std::string &name); + +class Pixmap { +public: + enum class Format { BGRA, RGBA, BGR, RGB, A }; + + Format format; + size_t width; + size_t height; + size_t stride; + std::vector data; + + static std::shared_ptr Create(Format format, size_t width, size_t height); + static std::shared_ptr FromPng(const uint8_t *data, size_t size, bool flip = false); + + static std::shared_ptr ReadPng(FILE *f, bool flip = false); + static std::shared_ptr ReadPng(const Platform::Path &filename, bool flip = false); + bool WritePng(FILE *f, bool flip = false); + bool WritePng(const Platform::Path &filename, bool flip = false); + + size_t GetBytesPerPixel() const; + RgbaColor GetPixel(size_t x, size_t y) const; + bool Equals(const Pixmap &other) const; + + void ConvertTo(Format newFormat); + void SetPixel(size_t x, size_t y, RgbaColor color); + + std::shared_ptr Copy(); +}; + +class BitmapFont { +public: + struct Glyph { + uint8_t advanceCells; + uint16_t position; + }; + + std::string unifontData; + std::map glyphs; + std::shared_ptr texture; + bool textureUpdated; + uint16_t nextPosition; + + static BitmapFont From(std::string &&unifontData); + static BitmapFont Create(); + + bool IsEmpty() const { return unifontData.empty(); } + const Glyph &GetGlyph(char32_t codepoint); + void LocateGlyph(char32_t codepoint, double *s0, double *t0, double *s1, double *t1, + size_t *advanceWidth, size_t *boundingHeight); + + void AddGlyph(char32_t codepoint, std::shared_ptr pixmap); + + size_t GetWidth(char32_t codepoint); + size_t GetWidth(const std::string &str); +}; + +class VectorFont { +public: + struct Contour { + std::vector points; + }; + + struct Glyph { + std::vector contours; + double leftSideBearing; + double boundingWidth; + double advanceWidth; + }; + + std::string lffData; + std::map glyphs; + double rightSideBearing; + double capHeight; + double ascender; + double descender; + + static VectorFont From(std::string &&lffData); + static VectorFont *Builtin(); + + bool IsEmpty() const { return lffData.empty(); } + const Glyph &GetGlyph(char32_t codepoint); + + double GetCapHeight(double forCapHeight) const; + double GetHeight(double forCapHeight) const; + double GetWidth(double forCapHeight, const std::string &str); + Vector GetExtents(double forCapHeight, const std::string &str); + + void Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str, + const std::function &traceEdge); + void Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str, + const std::function &traceEdge, const Camera &camera); +}; + +#endif diff --git a/src/sketch.h b/src/sketch.h index d9f1794..869e0b6 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -5,8 +5,8 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#ifndef __SKETCH_H -#define __SKETCH_H +#ifndef SOLVESPACE_SKETCH_H +#define SOLVESPACE_SKETCH_H class hGroup; class hRequest; @@ -21,6 +21,32 @@ class Param; class Equation; class Style; +enum class PolyError : uint32_t { + GOOD = 0, + NOT_CLOSED = 1, + NOT_COPLANAR = 2, + SELF_INTERSECTING = 3, + ZERO_LEN_EDGE = 4 +}; + +enum class StipplePattern : uint32_t { + CONTINUOUS = 0, + SHORT_DASH = 1, + DASH = 2, + LONG_DASH = 3, + DASH_DOT = 4, + DASH_DOT_DOT = 5, + DOT = 6, + FREEHAND = 7, + ZIGZAG = 8, + + LAST = ZIGZAG +}; + +const std::vector &StipplePatternDashes(StipplePattern pattern); +double StipplePatternLength(StipplePattern pattern); + +enum class Command : uint32_t; // All of the hWhatever handles are a 32-bit ID, that is used to represent // some data structure in the sketch. @@ -29,61 +55,88 @@ public: // bits 15: 0 -- group index uint32_t v; - inline hEntity entity(int i); - inline hParam param(int i); - inline hEquation equation(int i); + inline hEntity entity(int i) const; + inline hParam param(int i) const; + inline hEquation equation(int i) const; }; + +template<> +struct IsHandleOracle
: std::true_type {}; + class hRequest { public: // bits 15: 0 -- request index uint32_t v; - inline hEntity entity(int i); - inline hParam param(int i); + inline hEntity entity(int i) const; + inline hParam param(int i) const; - inline bool IsFromReferences(void); + inline bool IsFromReferences() const; }; + +template<> +struct IsHandleOracle : std::true_type {}; + class hEntity { public: // bits 15: 0 -- entity index // 31:16 -- request index uint32_t v; - inline bool isFromRequest(void); - inline hRequest request(void); - inline hGroup group(void); - inline hEquation equation(int i); + inline bool isFromRequest() const; + inline hRequest request() const; + inline hGroup group() const; + inline hEquation equation(int i) const; }; + +template<> +struct IsHandleOracle : std::true_type {}; + class hParam { public: // bits 15: 0 -- param index // 31:16 -- request index uint32_t v; - inline hRequest request(void); + inline hRequest request() const; }; +template<> +struct IsHandleOracle : std::true_type {}; + class hStyle { public: uint32_t v; }; +template<> +struct IsHandleOracle : std::true_type {}; -class EntityId { -public: +struct EntityId { uint32_t v; // entity ID, starting from 0 }; -class EntityMap { -public: - int tag; - EntityId h; +template<> +struct IsHandleOracle : std::true_type {}; + +struct EntityKey { hEntity input; int copyNumber; // (input, copyNumber) gets mapped to ((Request)xxx).entity(h.v) - - void Clear(void) {} }; +struct EntityKeyHash { + size_t operator()(const EntityKey &k) const { + size_t h1 = std::hash{}(k.input.v), + h2 = std::hash{}(k.copyNumber); + return h1 ^ (h2 << 1); + } +}; +struct EntityKeyEqual { + bool operator()(const EntityKey &a, const EntityKey &b) const { + return std::tie(a.input, a.copyNumber) == std::tie(b.input, b.copyNumber); + } +}; +typedef std::unordered_map EntityMap; // A set of requests. Every request must have an associated group. class Group { @@ -93,16 +146,26 @@ public: int tag; hGroup h; - enum { + enum class CopyAs { + NUMERIC, + N_TRANS, + N_ROT_AA, + N_ROT_TRANS, + N_ROT_AXIS_TRANS, + }; + + enum class Type : uint32_t { DRAWING_3D = 5000, DRAWING_WORKPLANE = 5001, EXTRUDE = 5100, LATHE = 5101, + REVOLVE = 5102, + HELIX = 5103, ROTATE = 5200, TRANSLATE = 5201, LINKED = 5300 }; - int type; + Group::Type type; int order; @@ -116,6 +179,7 @@ public: double scale; bool clean; + bool dofCheckOk; hEntity activeWorkplane; double valA; double valB; @@ -123,12 +187,14 @@ public: RgbaColor color; struct { - int how; + SolveResult how; int dof; + int findToFixTimeout; + bool timeout; List remove; } solved; - enum { + enum class Subtype : uint32_t { // For drawings in 2d WORKPLANE_BY_POINT_ORTHO = 6000, WORKPLANE_BY_LINE_SEGMENTS = 6001, @@ -136,7 +202,7 @@ public: ONE_SIDED = 7000, TWO_SIDED = 7001 }; - int subtype; + Group::Subtype subtype; bool skipFirst; // for step and repeat ops @@ -152,16 +218,10 @@ public: SPolygon polyLoops; SBezierLoopSetSet bezierLoops; - SBezierList bezierOpens; - enum { - POLY_GOOD = 0, - POLY_NOT_CLOSED = 1, - POLY_NOT_COPLANAR = 2, - POLY_SELF_INTERSECTING = 3, - POLY_ZERO_LEN_EDGE = 4 - }; + SBezierLoopSet bezierOpens; + struct { - int how; + PolyError how; SEdge notClosedAt; Vector errorPointAt; } polyError; @@ -176,24 +236,21 @@ public: bool displayDirty; SMesh displayMesh; - SEdgeList displayEdges; SOutlineList displayOutlines; - enum { - COMBINE_AS_UNION = 0, - COMBINE_AS_DIFFERENCE = 1, - COMBINE_AS_ASSEMBLE = 2 + enum class CombineAs : uint32_t { + UNION = 0, + DIFFERENCE = 1, + ASSEMBLE = 2, + INTERSECTION = 3 }; - int meshCombine; + CombineAs meshCombine; bool forceToMesh; - IdList remap; - enum { REMAP_PRIME = 19477 }; - int remapCache[REMAP_PRIME]; + EntityMap remap; - std::string linkFile; - std::string linkFileRel; + Platform::Path linkFile; SMesh impMesh; SShell impShell; EntityList impEntity; @@ -201,14 +258,16 @@ public: std::string name; - void Activate(void); - std::string DescriptionString(void); - void Clear(void); + void Activate(); + std::string DescriptionString(); + void Clear(); static void AddParam(ParamList *param, hParam hp, double v); void Generate(EntityList *entity, ParamList *param); bool IsSolvedOkay(); void TransformImportedBy(Vector t, Quaternion q); + bool IsForcedToMeshBySource() const; + bool IsForcedToMesh() const; // When a request generates entities from entities, and the source // entities may have come from multiple requests, it's necessary to // remap the entity ID so that it's still unique. We do this with a @@ -223,46 +282,52 @@ public: REMAP_LATHE_END = 1007, REMAP_PT_TO_ARC = 1008, REMAP_PT_TO_NORMAL = 1009, + REMAP_LATHE_ARC_CENTER = 1010, }; hEntity Remap(hEntity in, int copyNumber); void MakeExtrusionLines(EntityList *el, hEntity in); - void MakeLatheCircles(IdList *el, IdList *param, hEntity in, Vector pt, Vector axis, int ai); + void MakeLatheCircles(IdList *el, IdList *param, hEntity in, Vector pt, Vector axis); + void MakeLatheSurfacesSelectable(IdList *el, hEntity in, Vector axis); + void MakeRevolveEndFaces(IdList *el, hEntity pt, int ai, int af); void MakeExtrusionTopBottomFaces(EntityList *el, hEntity pt); void CopyEntity(EntityList *el, Entity *ep, int timesApplied, int remap, hParam dx, hParam dy, hParam dz, - hParam qw, hParam qvx, hParam qvy, hParam qvz, - bool asTrans, bool asAxisAngle); + hParam qw, hParam qvx, hParam qvy, hParam qvz, hParam dist, + CopyAs as); void AddEq(IdList *l, Expr *expr, int index); void GenerateEquations(IdList *l); - bool IsVisible(void); - int GetNumConstraints(); + bool IsVisible(); + size_t GetNumConstraints(); Vector ExtrusionGetVector(); void ExtrusionForceVectorTo(const Vector &v); // Assembling the curves into loops, and into a piecewise linear polygon // at the same time. void AssembleLoops(bool *allClosed, bool *allCoplanar, bool *allNonZeroLen); - void GenerateLoops(void); + void GenerateLoops(); // And the mesh stuff - Group *PreviousGroup(void); - Group *RunningMeshGroup(void); + Group *PreviousGroup() const; + Group *RunningMeshGroup() const; bool IsMeshGroup(); - void GenerateShellAndMesh(void); - template void GenerateForStepAndRepeat(T *steps, T *outs); - template void GenerateForBoolean(T *a, T *b, T *o, int how); - void GenerateDisplayItems(void); - void DrawDisplayItems(int t); - void Draw(void); - RgbaColor GetLoopSetFillColor(SBezierLoopSet *sbls, - bool *allSame, Vector *errorAt); - void FillLoopSetAsPolygon(SBezierLoopSet *sbls); - void DrawFilledPaths(void); - - SPolygon GetPolygon(void); - - static void MenuGroup(int id); + + void GenerateShellAndMesh(); + template void GenerateForStepAndRepeat(T *steps, T *outs, Group::CombineAs forWhat); + template void GenerateForBoolean(T *a, T *b, T *o, Group::CombineAs how); + void GenerateDisplayItems(); + + enum class DrawMeshAs { DEFAULT, HOVERED, SELECTED }; + void DrawMesh(DrawMeshAs how, Canvas *canvas); + void Draw(Canvas *canvas); + void DrawPolyError(Canvas *canvas); + void DrawFilledPaths(Canvas *canvas); + void DrawContourAreaLabels(Canvas *canvas); + + SPolygon GetPolygon(); + + static void MenuGroup(Command id); + static void MenuGroup(Command id, Platform::Path linkFile); }; // A user request for some primitive or derived operation; for example a @@ -278,7 +343,7 @@ public: hRequest h; // Types of requests - enum { + enum class Type : uint32_t { WORKPLANE = 100, DATUM_POINT = 101, LINE_SEGMENT = 200, @@ -286,10 +351,11 @@ public: CUBIC_PERIODIC = 301, CIRCLE = 400, ARC_OF_CIRCLE = 500, - TTF_TEXT = 600 + TTF_TEXT = 600, + IMAGE = 700 }; - int type; + Request::Type type; int extraPoints; hEntity workplane; // or Entity::FREE_IN_3D @@ -297,16 +363,19 @@ public: hStyle style; bool construction; + std::string str; std::string font; + Platform::Path file; + double aspectRatio; static hParam AddParam(ParamList *param, hParam hp); void Generate(EntityList *entity, ParamList *param); - std::string DescriptionString(void); - int IndexOfPoint(hEntity he); + std::string DescriptionString() const; + int IndexOfPoint(hEntity he) const; - void Clear(void) {} + void Clear() {} }; #define MAX_POINTS_IN_ENTITY (12) @@ -318,13 +387,14 @@ public: static const hEntity FREE_IN_3D; static const hEntity NO_ENTITY; - enum { + enum class Type : uint32_t { POINT_IN_3D = 2000, POINT_IN_2D = 2001, POINT_N_TRANS = 2010, POINT_N_ROT_TRANS = 2011, POINT_N_COPY = 2012, POINT_N_ROT_AA = 2013, + POINT_N_ROT_AXIS_TRANS = 2014, NORMAL_IN_3D = 3000, NORMAL_IN_2D = 3001, @@ -340,7 +410,8 @@ public: FACE_N_ROT_TRANS = 5002, FACE_N_TRANS = 5003, FACE_N_ROT_AA = 5004, - + FACE_ROT_NORMAL_PT = 5005, + FACE_N_ROT_AXIS_TRANS = 5006, WORKPLANE = 10000, LINE_SEGMENT = 11000, @@ -348,10 +419,11 @@ public: CUBIC_PERIODIC = 12001, CIRCLE = 13000, ARC_OF_CIRCLE = 14000, - TTF_TEXT = 15000 + TTF_TEXT = 15000, + IMAGE = 16000 }; - int type; + Type type; hGroup group; hEntity workplane; // or Entity::FREE_IN_3D @@ -364,7 +436,7 @@ public: hEntity distance; // The only types that have their own params are points, normals, // and directions. - hParam param[7]; + hParam param[8]; // Transformed points/normals/distances have their numerical base Vector numPoint; @@ -373,82 +445,90 @@ public: std::string str; std::string font; + Platform::Path file; + double aspectRatio; // For entities that are derived by a transformation, the number of // times to apply the transformation. int timesApplied; - Quaternion GetAxisAngleQuaternion(int param0); - ExprQuaternion GetAxisAngleQuaternionExprs(int param0); + Quaternion GetAxisAngleQuaternion(int param0) const; + ExprQuaternion GetAxisAngleQuaternionExprs(int param0) const; - bool IsCircle(void); - Expr *CircleGetRadiusExpr(void); - double CircleGetRadiusNum(void); - void ArcGetAngles(double *thetaa, double *thetab, double *dtheta); + bool IsCircle() const; + Expr *CircleGetRadiusExpr() const; + double CircleGetRadiusNum() const; + void ArcGetAngles(double *thetaa, double *thetab, double *dtheta) const; - bool HasVector(void); - ExprVector VectorGetExprs(void); - Vector VectorGetNum(void); - Vector VectorGetRefPoint(void); - Vector VectorGetStartPoint(void); + bool HasVector() const; + ExprVector VectorGetExprs() const; + ExprVector VectorGetExprsInWorkplane(hEntity wrkpl) const; + Vector VectorGetNum() const; + Vector VectorGetRefPoint() const; + Vector VectorGetStartPoint() const; // For distances - bool IsDistance(void); - double DistanceGetNum(void); - Expr *DistanceGetExpr(void); + bool IsDistance() const; + double DistanceGetNum() const; + Expr *DistanceGetExpr() const; void DistanceForceTo(double v); - bool IsWorkplane(void); + bool IsWorkplane() const; // The plane is points P such that P dot (xn, yn, zn) - d = 0 - void WorkplaneGetPlaneExprs(ExprVector *n, Expr **d); - ExprVector WorkplaneGetOffsetExprs(void); - Vector WorkplaneGetOffset(void); - EntityBase *Normal(void); - - bool IsFace(void); - ExprVector FaceGetNormalExprs(void); - Vector FaceGetNormalNum(void); - ExprVector FaceGetPointExprs(void); - Vector FaceGetPointNum(void); - - bool IsPoint(void); + void WorkplaneGetPlaneExprs(ExprVector *n, Expr **d) const; + ExprVector WorkplaneGetOffsetExprs() const; + Vector WorkplaneGetOffset() const; + EntityBase *Normal() const; + + bool IsFace() const; + ExprVector FaceGetNormalExprs() const; + Vector FaceGetNormalNum() const; + ExprVector FaceGetPointExprs() const; + Vector FaceGetPointNum() const; + + bool IsPoint() const; // Applies for any of the point types - Vector PointGetNum(void); - ExprVector PointGetExprs(void); - void PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v); + Vector PointGetNum() const; + ExprVector PointGetExprs() const; + void PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) const; + ExprVector PointGetExprsInWorkplane(hEntity wrkpl) const; void PointForceTo(Vector v); + void PointForceParamTo(Vector v); // These apply only the POINT_N_ROT_TRANS, which has an assoc rotation - Quaternion PointGetQuaternion(void); + Quaternion PointGetQuaternion() const; void PointForceQuaternionTo(Quaternion q); - bool IsNormal(void); + bool IsNormal() const; // Applies for any of the normal types - Quaternion NormalGetNum(void); - ExprQuaternion NormalGetExprs(void); + Quaternion NormalGetNum() const; + ExprQuaternion NormalGetExprs() const; void NormalForceTo(Quaternion q); - Vector NormalU(void); - Vector NormalV(void); - Vector NormalN(void); - ExprVector NormalExprsU(void); - ExprVector NormalExprsV(void); - ExprVector NormalExprsN(void); + Vector NormalU() const; + Vector NormalV() const; + Vector NormalN() const; + ExprVector NormalExprsU() const; + ExprVector NormalExprsV() const; + ExprVector NormalExprsN() const; - Vector CubicGetStartNum(void); - Vector CubicGetFinishNum(void); - ExprVector CubicGetStartTangentExprs(void); - ExprVector CubicGetFinishTangentExprs(void); - Vector CubicGetStartTangentNum(void); - Vector CubicGetFinishTangentNum(void); + Vector CubicGetStartNum() const; + Vector CubicGetFinishNum() const; + ExprVector CubicGetStartTangentExprs() const; + ExprVector CubicGetFinishTangentExprs() const; + Vector CubicGetStartTangentNum() const; + Vector CubicGetFinishTangentNum() const; - bool HasEndpoints(void); - Vector EndpointStart(); - Vector EndpointFinish(); + bool HasEndpoints() const; + Vector EndpointStart() const; + Vector EndpointFinish() const; + bool IsInPlane(Vector norm, double distance) const; - void AddEq(IdList *l, Expr *expr, int index); - void GenerateEquations(IdList *l); + void RectGetPointsExprs(ExprVector *eap, ExprVector *ebp) const; - void Clear(void) {} + void AddEq(IdList *l, Expr *expr, int index) const; + void GenerateEquations(IdList *l) const; + + void Clear() {} }; class Entity : public EntityBase { @@ -462,8 +542,7 @@ public: // POD members with indeterminate value. Entity() : EntityBase({}), forceHidden(), actPoint(), actNormal(), actDistance(), actVisible(), style(), construction(), - beziers(), edges(), edgesChordTol(), screenBBox(), screenBBoxValid(), - dogd() {}; + beziers(), edges(), edgesChordTol(), screenBBox(), screenBBoxValid() {}; // A linked entity that was hidden in the source file ends up hidden // here too. @@ -487,42 +566,27 @@ public: BBox screenBBox; bool screenBBoxValid; - // Routines to draw and hit-test the representation of the entity - // on-screen. - struct { - bool drawing; - Point2d mp; - double dmin; - Vector refp; - double lineWidth; - double stippleScale; - int stippleType; - int data; - } dogd; // state for drawing or getting distance (for hit testing) - void LineDrawOrGetDistance(Vector a, Vector b, bool maybeFat=false, int userData = -1); - void DrawOrGetDistance(void); - - bool IsStylable(); - bool IsVisible(void); - bool PointIsFromReferences(void); - - void ComputeInterpolatingSpline(SBezierList *sbl, bool periodic); - void GenerateBezierCurves(SBezierList *sbl); - void GenerateEdges(SEdgeList *el, bool includingConstruction=false); - - static void DrawAll(bool drawAsHidden); - void Draw(bool drawAsHidden); - double GetDistance(Point2d mp); - Vector GetReferencePos(void); + bool IsStylable() const; + bool IsVisible() const; + bool CanBeDragged() const; - void CalculateNumerical(bool forExport); + enum class DrawAs { DEFAULT, OVERLAY, HIDDEN, HOVERED, SELECTED }; + void Draw(DrawAs how, Canvas *canvas); + void GetReferencePoints(std::vector *refs); + int GetPositionOfPoint(const Camera &camera, Point2d p); - std::string DescriptionString(void); + void ComputeInterpolatingSpline(SBezierList *sbl, bool periodic) const; + void GenerateBezierCurves(SBezierList *sbl) const; + void GenerateEdges(SEdgeList *el); SBezierList *GetOrGenerateBezierCurves(); SEdgeList *GetOrGenerateEdges(); BBox GetOrGenerateScreenBBox(bool *hasBBox); + void CalculateNumerical(bool forExport); + + std::string DescriptionString() const; + void Clear() { beziers.l.Clear(); edges.l.Clear(); @@ -531,26 +595,11 @@ public: class EntReqTable { public: - typedef struct { - int reqType; - int entType; - int points; - bool useExtraPoints; - bool hasNormal; - bool hasDistance; - const char *description; - } TableEntry; - - static const TableEntry Table[]; - - static const char *DescriptionForRequest(int req); - static void CopyEntityInfo(const TableEntry *te, int extraPoints, - int *ent, int *req, int *pts, bool *hasNormal, bool *hasDistance); - static bool GetRequestInfo(int req, int extraPoints, - int *ent, int *pts, bool *hasNormal, bool *hasDistance); - static bool GetEntityInfo(int ent, int extraPoints, - int *req, int *pts, bool *hasNormal, bool *hasDistance); - static int GetRequestForEntity(int ent); + static bool GetRequestInfo(Request::Type req, int extraPoints, + EntityBase::Type *ent, int *pts, bool *hasNormal, bool *hasDistance); + static bool GetEntityInfo(EntityBase::Type ent, int extraPoints, + Request::Type *req, int *pts, bool *hasNormal, bool *hasDistance); + static Request::Type GetRequestForEntity(EntityBase::Type ent); }; class Param { @@ -567,7 +616,7 @@ public: static const hParam NO_PARAM; - void Clear(void) {} + void Clear() {} }; @@ -575,9 +624,13 @@ class hConstraint { public: uint32_t v; - inline hEquation equation(int i); + inline hEquation equation(int i) const; + inline hParam param(int i) const; }; +template<> +struct IsHandleOracle : std::true_type {}; + class ConstraintBase { public: int tag; @@ -585,7 +638,7 @@ public: static const hConstraint NO_CONSTRAINT; - enum { + enum class Type : uint32_t { POINTS_COINCIDENT = 20, PT_PT_DISTANCE = 30, PT_PLANE_DISTANCE = 31, @@ -624,13 +677,14 @@ public: COMMENT = 1000 }; - int type; + Type type; hGroup group; hEntity workplane; // These are the parameters for the constraint. double valA; + hParam valP; hEntity ptA; hEntity ptB; hEntity entityA; @@ -643,27 +697,40 @@ public: bool reference; // a ref dimension, that generates no eqs std::string comment; // since comments are represented as constraints - bool HasLabel(void); + bool Equals(const ConstraintBase &c) const { + return type == c.type && group == c.group && workplane == c.workplane && + valA == c.valA && valP == c.valP && ptA == c.ptA && ptB == c.ptB && + entityA == c.entityA && entityB == c.entityB && + entityC == c.entityC && entityD == c.entityD && + other == c.other && other2 == c.other2 && reference == c.reference && + comment == c.comment; + } + + bool HasLabel() const; + bool IsProjectible() const; - void Generate(IdList *l); - void GenerateReal(IdList *l); + void Generate(IdList *param); + + void GenerateEquations(IdList *entity, + bool forReference = false) const; // Some helpers when generating symbolic constraint equations - void ModifyToSatisfy(void); - void AddEq(IdList *l, Expr *expr, int index); + void ModifyToSatisfy(); + void AddEq(IdList *l, Expr *expr, int index) const; + void AddEq(IdList *l, const ExprVector &v, int baseIndex = 0) const; static Expr *DirectionCosine(hEntity wrkpl, ExprVector ae, ExprVector be); static Expr *Distance(hEntity workplane, hEntity pa, hEntity pb); static Expr *PointLineDistance(hEntity workplane, hEntity pt, hEntity ln); static Expr *PointPlaneDistance(ExprVector p, hEntity plane); - static Expr *VectorsParallel(int eq, ExprVector a, ExprVector b); + static ExprVector VectorsParallel3d(ExprVector a, ExprVector b, hParam p); static ExprVector PointInThreeSpace(hEntity workplane, Expr *u, Expr *v); - void Clear(void) {} + void Clear() {} }; class Constraint : public ConstraintBase { public: // See Entity::Entity(). - Constraint() : ConstraintBase({}), disp(), dogd() {} + Constraint() : ConstraintBase({}), disp() {} // These define how the constraint is drawn on-screen. struct { @@ -671,63 +738,71 @@ public: hStyle style; } disp; - // State for drawing or getting distance (for hit testing). - struct { - bool drawing; - Point2d mp; - double dmin; - Vector refp; - SEdgeList *sel; - } dogd; - - double GetDistance(Point2d mp); - Vector GetLabelPos(void); - Vector GetReferencePos(void); - void Draw(void); - void GetEdges(SEdgeList *sel); - bool IsStylable(); - hStyle GetStyle() const; - bool HasLabel(); - - void LineDrawOrGetDistance(Vector a, Vector b); bool IsVisible() const; - void DrawOrGetDistance(Vector *labelPos); - std::string Label(void); - bool DoLineExtend(Vector p0, Vector p1, Vector pt, double salient); - void DoArcForAngle(Vector a0, Vector da, Vector b0, Vector db, - Vector offset, Vector *ref, bool trim); - void DoArrow(Vector p, Vector dir, Vector n, double width, double angle, double da); - void DoLineWithArrows(Vector ref, Vector a, Vector b, bool onlyOneExt); - int DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool extend, Vector gr, Vector gu, double swidth, double sheight); - int DoLineTrimmedAgainstBox(Vector ref, Vector a, Vector b, bool extend = true); - void DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu); - void StippledLine(Vector a, Vector b); - void DoProjectedPoint(Vector *p); - void DoEqualLenTicks(Vector a, Vector b, Vector gn); - void DoEqualRadiusTicks(hEntity he); - - std::string DescriptionString(void); - - static hConstraint AddConstraint(Constraint *c, bool rememberForUndo); - static hConstraint AddConstraint(Constraint *c); - static void MenuConstrain(int id); - static void DeleteAllConstraintsFor(int type, hEntity entityA, hEntity ptA); + bool IsStylable() const; + hStyle GetStyle() const; + bool HasLabel() const; + std::string Label() const; + + enum class DrawAs { DEFAULT, HOVERED, SELECTED }; + void Draw(DrawAs how, Canvas *canvas); + Vector GetLabelPos(const Camera &camera); + void GetReferencePoints(const Camera &camera, std::vector *refs); + + void DoLayout(DrawAs how, Canvas *canvas, + Vector *labelPos, std::vector *refs); + void DoLine(Canvas *canvas, Canvas::hStroke hcs, Vector a, Vector b); + void DoStippledLine(Canvas *canvas, Canvas::hStroke hcs, Vector a, Vector b); + bool DoLineExtend(Canvas *canvas, Canvas::hStroke hcs, + Vector p0, Vector p1, Vector pt, double salient); + void DoArcForAngle(Canvas *canvas, Canvas::hStroke hcs, + Vector a0, Vector da, Vector b0, Vector db, + Vector offset, Vector *ref, bool trim); + void DoArrow(Canvas *canvas, Canvas::hStroke hcs, + Vector p, Vector dir, Vector n, double width, double angle, double da); + void DoLineWithArrows(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool onlyOneExt); + int DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool extend, + Vector gr, Vector gu, double swidth, double sheight); + int DoLineTrimmedAgainstBox(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector a, Vector b, bool extend = true); + void DoLabel(Canvas *canvas, Canvas::hStroke hcs, + Vector ref, Vector *labelPos, Vector gr, Vector gu); + void DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs, Vector *p); + void DoProjectedPoint(Canvas *canvas, Canvas::hStroke hcs, Vector *p, Vector n, Vector o); + + void DoEqualLenTicks(Canvas *canvas, Canvas::hStroke hcs, + Vector a, Vector b, Vector gn, Vector *refp); + void DoEqualRadiusTicks(Canvas *canvas, Canvas::hStroke hcs, + hEntity he, Vector *refp); + + std::string DescriptionString() const; + + static hConstraint AddConstraint(Constraint *c, bool rememberForUndo = true); + static void MenuConstrain(Command id); + static void DeleteAllConstraintsFor(Constraint::Type type, hEntity entityA, hEntity ptA); static hConstraint ConstrainCoincident(hEntity ptA, hEntity ptB); - static hConstraint Constrain(int type, hEntity ptA, hEntity ptB, hEntity entityA); - static hConstraint Constrain(int type, hEntity ptA, hEntity ptB, - hEntity entityA, hEntity entityB, - bool other, bool other2); + static hConstraint Constrain(Constraint::Type type, hEntity ptA, hEntity ptB, hEntity entityA, + hEntity entityB = Entity::NO_ENTITY, bool other = false, + bool other2 = false); + static hConstraint TryConstrain(Constraint::Type type, hEntity ptA, hEntity ptB, + hEntity entityA, hEntity entityB = Entity::NO_ENTITY, + bool other = false, bool other2 = false); }; class hEquation { public: uint32_t v; - inline bool isFromConstraint(void); - inline hConstraint constraint(void); + inline bool isFromConstraint() const; + inline hConstraint constraint() const; }; +template<> +struct IsHandleOracle : std::true_type {}; + class Equation { public: int tag; @@ -735,7 +810,7 @@ public: Expr *e; - void Clear(void) {} + void Clear() {} }; @@ -744,19 +819,6 @@ public: int tag; hStyle h; - enum { - STIPPLE_CONTINUOUS = 0, - STIPPLE_DASH = 1, - STIPPLE_LONG_DASH = 2, - STIPPLE_DASH_DOT = 3, - STIPPLE_DASH_DOT_DOT = 4, - STIPPLE_DOT = 5, - STIPPLE_FREEHAND = 6, - STIPPLE_ZIGZAG = 7, - - LAST_STIPPLE = STIPPLE_ZIGZAG - }; - enum { // If an entity has no style, then it will be colored according to // whether the group that it's in is active or not, whether it's @@ -784,28 +846,29 @@ public: std::string name; - enum { - UNITS_AS_PIXELS = 0, - UNITS_AS_MM = 1 + enum class UnitsAs : uint32_t { + PIXELS = 0, + MM = 1 }; double width; - int widthAs; + UnitsAs widthAs; double textHeight; - int textHeightAs; - enum { - ORIGIN_LEFT = 0x01, - ORIGIN_RIGHT = 0x02, - ORIGIN_BOT = 0x04, - ORIGIN_TOP = 0x08 + UnitsAs textHeightAs; + enum class TextOrigin : uint32_t { + NONE = 0x00, + LEFT = 0x01, + RIGHT = 0x02, + BOT = 0x04, + TOP = 0x08 }; - int textOrigin; + TextOrigin textOrigin; double textAngle; RgbaColor color; bool filled; RgbaColor fillColor; bool visible; bool exportable; - int stippleType; + StipplePattern stippleType; double stippleScale; int zIndex; @@ -825,11 +888,11 @@ public: static std::string CnfTextHeight(const std::string &prefix); static std::string CnfPrefixToName(const std::string &prefix); - static void CreateAllDefaultStyles(void); + static void CreateAllDefaultStyles(); static void CreateDefaultStyle(hStyle h); static void FillDefaultStyle(Style *s, const Default *d = NULL, bool factory = false); - static void FreezeDefaultStyles(void); - static void LoadFactoryDefaults(void); + static void FreezeDefaultStyles(Platform::SettingsRef settings); + static void LoadFactoryDefaults(); static void AssignSelectionToStyle(uint32_t v); static uint32_t CreateCustomStyle(bool rememberForUndo = true); @@ -838,71 +901,76 @@ public: static Style *Get(hStyle hs); static RgbaColor Color(hStyle hs, bool forExport=false); - static RgbaColor FillColor(hStyle hs, bool forExport=false); - static float Width(hStyle hs); static RgbaColor Color(int hs, bool forExport=false); - static float Width(int hs); + static RgbaColor FillColor(hStyle hs, bool forExport=false); + static double Width(hStyle hs); + static double Width(int hs); static double WidthMm(int hs); static double TextHeight(hStyle hs); static double DefaultTextHeight(); + static Canvas::Stroke Stroke(hStyle hs); + static Canvas::Stroke Stroke(int hs); static bool Exportable(int hs); static hStyle ForEntity(hEntity he); - static int PatternType(hStyle hs); + static StipplePattern PatternType(hStyle hs); static double StippleScaleMm(hStyle hs); - std::string DescriptionString(void); + std::string DescriptionString() const; - void Clear(void) {} + void Clear() {} }; -inline hEntity hGroup::entity(int i) +inline hEntity hGroup::entity(int i) const { hEntity r; r.v = 0x80000000 | (v << 16) | (uint32_t)i; return r; } -inline hParam hGroup::param(int i) +inline hParam hGroup::param(int i) const { hParam r; r.v = 0x80000000 | (v << 16) | (uint32_t)i; return r; } -inline hEquation hGroup::equation(int i) +inline hEquation hGroup::equation(int i) const { hEquation r; r.v = (v << 16) | 0x80000000 | (uint32_t)i; return r; } -inline bool hRequest::IsFromReferences(void) { - if(v == Request::HREQUEST_REFERENCE_XY.v) return true; - if(v == Request::HREQUEST_REFERENCE_YZ.v) return true; - if(v == Request::HREQUEST_REFERENCE_ZX.v) return true; +inline bool hRequest::IsFromReferences() const { + if(*this == Request::HREQUEST_REFERENCE_XY) return true; + if(*this == Request::HREQUEST_REFERENCE_YZ) return true; + if(*this == Request::HREQUEST_REFERENCE_ZX) return true; return false; } -inline hEntity hRequest::entity(int i) +inline hEntity hRequest::entity(int i) const { hEntity r; r.v = (v << 16) | (uint32_t)i; return r; } -inline hParam hRequest::param(int i) +inline hParam hRequest::param(int i) const { hParam r; r.v = (v << 16) | (uint32_t)i; return r; } -inline bool hEntity::isFromRequest(void) +inline bool hEntity::isFromRequest() const { if(v & 0x80000000) return false; else return true; } -inline hRequest hEntity::request(void) +inline hRequest hEntity::request() const { hRequest r; r.v = (v >> 16); return r; } -inline hGroup hEntity::group(void) +inline hGroup hEntity::group() const { hGroup r; r.v = (v >> 16) & 0x3fff; return r; } -inline hEquation hEntity::equation(int i) - { if(i != 0) oops(); hEquation r; r.v = v | 0x40000000; return r; } +inline hEquation hEntity::equation(int i) const + { hEquation r; r.v = v | 0x40000000 | (uint32_t)i; return r; } -inline hRequest hParam::request(void) +inline hRequest hParam::request() const { hRequest r; r.v = (v >> 16); return r; } -inline hEquation hConstraint::equation(int i) +inline hEquation hConstraint::equation(int i) const { hEquation r; r.v = (v << 16) | (uint32_t)i; return r; } +inline hParam hConstraint::param(int i) const + { hParam r; r.v = v | 0x40000000 | (uint32_t)i; return r; } -inline bool hEquation::isFromConstraint(void) +inline bool hEquation::isFromConstraint() const { if(v & 0xc0000000) return false; else return true; } -inline hConstraint hEquation::constraint(void) +inline hConstraint hEquation::constraint() const { hConstraint r; r.v = (v >> 16); return r; } // The format for entities stored on the clipboard. class ClipboardRequest { public: - int type; + Request::Type type; int extraPoints; hStyle style; std::string str; std::string font; + Platform::Path file; bool construction; Vector point[MAX_POINTS_IN_ENTITY]; diff --git a/src/solvespace.cpp b/src/solvespace.cpp index c923b7b..7c30a8f 100644 --- a/src/solvespace.cpp +++ b/src/solvespace.cpp @@ -10,253 +10,378 @@ SolveSpaceUI SolveSpace::SS = {}; Sketch SolveSpace::SK = {}; -std::string SolveSpace::RecentFile[MAX_RECENT] = {}; - void SolveSpaceUI::Init() { +#if !defined(HEADLESS) + // Check that the resource system works. + dbp("%s", LoadString("banner.txt").data()); +#endif + + Platform::SettingsRef settings = Platform::GetSettings(); + SS.tangentArcRadius = 10.0; // Then, load the registry settings. - int i; // Default list of colors for the model material - modelColor[0] = CnfThawColor(RGBi(150, 150, 150), "ModelColor_0"); - modelColor[1] = CnfThawColor(RGBi(100, 100, 100), "ModelColor_1"); - modelColor[2] = CnfThawColor(RGBi( 30, 30, 30), "ModelColor_2"); - modelColor[3] = CnfThawColor(RGBi(150, 0, 0), "ModelColor_3"); - modelColor[4] = CnfThawColor(RGBi( 0, 100, 0), "ModelColor_4"); - modelColor[5] = CnfThawColor(RGBi( 0, 80, 80), "ModelColor_5"); - modelColor[6] = CnfThawColor(RGBi( 0, 0, 130), "ModelColor_6"); - modelColor[7] = CnfThawColor(RGBi( 80, 0, 80), "ModelColor_7"); + modelColor[0] = settings->ThawColor("ModelColor_0", RGBi(150, 150, 150)); + modelColor[1] = settings->ThawColor("ModelColor_1", RGBi(100, 100, 100)); + modelColor[2] = settings->ThawColor("ModelColor_2", RGBi( 30, 30, 30)); + modelColor[3] = settings->ThawColor("ModelColor_3", RGBi(150, 0, 0)); + modelColor[4] = settings->ThawColor("ModelColor_4", RGBi( 0, 100, 0)); + modelColor[5] = settings->ThawColor("ModelColor_5", RGBi( 0, 80, 80)); + modelColor[6] = settings->ThawColor("ModelColor_6", RGBi( 0, 0, 130)); + modelColor[7] = settings->ThawColor("ModelColor_7", RGBi( 80, 0, 80)); // Light intensities - lightIntensity[0] = CnfThawFloat(1.0f, "LightIntensity_0"); - lightIntensity[1] = CnfThawFloat(0.5f, "LightIntensity_1"); - ambientIntensity = 0.3; // no setting for that yet + lightIntensity[0] = settings->ThawFloat("LightIntensity_0", 1.0); + lightIntensity[1] = settings->ThawFloat("LightIntensity_1", 0.5); + ambientIntensity = settings->ThawFloat("Light_Ambient", 0.3); // Light positions - lightDir[0].x = CnfThawFloat(-1.0f, "LightDir_0_Right" ); - lightDir[0].y = CnfThawFloat( 1.0f, "LightDir_0_Up" ); - lightDir[0].z = CnfThawFloat( 0.0f, "LightDir_0_Forward" ); - lightDir[1].x = CnfThawFloat( 1.0f, "LightDir_1_Right" ); - lightDir[1].y = CnfThawFloat( 0.0f, "LightDir_1_Up" ); - lightDir[1].z = CnfThawFloat( 0.0f, "LightDir_1_Forward" ); + lightDir[0].x = settings->ThawFloat("LightDir_0_Right", -1.0); + lightDir[0].y = settings->ThawFloat("LightDir_0_Up", 1.0); + lightDir[0].z = settings->ThawFloat("LightDir_0_Forward", 0.0); + lightDir[1].x = settings->ThawFloat("LightDir_1_Right", 1.0); + lightDir[1].y = settings->ThawFloat("LightDir_1_Up", 0.0); + lightDir[1].z = settings->ThawFloat("LightDir_1_Forward", 0.0); exportMode = false; // Chord tolerance - chordTol = CnfThawFloat(0.5f, "ChordTolerancePct"); + chordTol = settings->ThawFloat("ChordTolerancePct", 0.1); // Max pwl segments to generate - maxSegments = CnfThawInt(10, "MaxSegments"); + maxSegments = settings->ThawInt("MaxSegments", 20); // Chord tolerance - exportChordTol = CnfThawFloat(0.1f, "ExportChordTolerance"); + exportChordTol = settings->ThawFloat("ExportChordTolerance", 0.1); // Max pwl segments to generate - exportMaxSegments = CnfThawInt(64, "ExportMaxSegments"); + exportMaxSegments = settings->ThawInt("ExportMaxSegments", 64); + // Timeout value for finding redundant constrains (ms) + timeoutRedundantConstr = settings->ThawInt("TimeoutRedundantConstraints", 1000); // View units - viewUnits = (Unit)CnfThawInt((uint32_t)UNIT_MM, "ViewUnits"); + viewUnits = (Unit)settings->ThawInt("ViewUnits", (uint32_t)Unit::MM); // Number of digits after the decimal point - afterDecimalMm = CnfThawInt(2, "AfterDecimalMm"); - afterDecimalInch = CnfThawInt(3, "AfterDecimalInch"); + afterDecimalMm = settings->ThawInt("AfterDecimalMm", 2); + afterDecimalInch = settings->ThawInt("AfterDecimalInch", 3); + afterDecimalDegree = settings->ThawInt("AfterDecimalDegree", 2); + useSIPrefixes = settings->ThawBool("UseSIPrefixes", false); // Camera tangent (determines perspective) - cameraTangent = CnfThawFloat(0.3f/1e3f, "CameraTangent"); + cameraTangent = settings->ThawFloat("CameraTangent", 0.3f/1e3); // Grid spacing - gridSpacing = CnfThawFloat(5.0f, "GridSpacing"); + gridSpacing = settings->ThawFloat("GridSpacing", 5.0); // Export scale factor - exportScale = CnfThawFloat(1.0f, "ExportScale"); + exportScale = settings->ThawFloat("ExportScale", 1.0); // Export offset (cutter radius comp) - exportOffset = CnfThawFloat(0.0f, "ExportOffset"); + exportOffset = settings->ThawFloat("ExportOffset", 0.0); // Rewrite exported colors close to white into black (assuming white bg) - fixExportColors = CnfThawBool(true, "FixExportColors"); + fixExportColors = settings->ThawBool("FixExportColors", true); + // Export background color + exportBackgroundColor = settings->ThawBool("ExportBackgroundColor", false); // Draw back faces of triangles (when mesh is leaky/self-intersecting) - drawBackFaces = CnfThawBool(true, "DrawBackFaces"); + drawBackFaces = settings->ThawBool("DrawBackFaces", true); + // Use turntable mouse navigation + turntableNav = settings->ThawBool("TurntableNav", false); + // Immediately edit dimension + immediatelyEditDimension = settings->ThawBool("ImmediatelyEditDimension", false); // Check that contours are closed and not self-intersecting - checkClosedContour = CnfThawBool(true, "CheckClosedContour"); + checkClosedContour = settings->ThawBool("CheckClosedContour", true); + // Enable automatic constrains for lines + automaticLineConstraints = settings->ThawBool("AutomaticLineConstraints", true); + // Draw closed polygons areas + showContourAreas = settings->ThawBool("ShowContourAreas", false); // Export shaded triangles in a 2d view - exportShadedTriangles = CnfThawBool(true, "ExportShadedTriangles"); + exportShadedTriangles = settings->ThawBool("ExportShadedTriangles", true); // Export pwl curves (instead of exact) always - exportPwlCurves = CnfThawBool(false, "ExportPwlCurves"); + exportPwlCurves = settings->ThawBool("ExportPwlCurves", false); // Background color on-screen - backgroundColor = CnfThawColor(RGBi(0, 0, 0), "BackgroundColor"); + backgroundColor = settings->ThawColor("BackgroundColor", RGBi(0, 0, 0)); // Whether export canvas size is fixed or derived from bbox - exportCanvasSizeAuto = CnfThawBool(true, "ExportCanvasSizeAuto"); + exportCanvasSizeAuto = settings->ThawBool("ExportCanvasSizeAuto", true); // Margins for automatic canvas size - exportMargin.left = CnfThawFloat(5.0f, "ExportMargin_Left"); - exportMargin.right = CnfThawFloat(5.0f, "ExportMargin_Right"); - exportMargin.bottom = CnfThawFloat(5.0f, "ExportMargin_Bottom"); - exportMargin.top = CnfThawFloat(5.0f, "ExportMargin_Top"); + exportMargin.left = settings->ThawFloat("ExportMargin_Left", 5.0); + exportMargin.right = settings->ThawFloat("ExportMargin_Right", 5.0); + exportMargin.bottom = settings->ThawFloat("ExportMargin_Bottom", 5.0); + exportMargin.top = settings->ThawFloat("ExportMargin_Top", 5.0); // Dimensions for fixed canvas size - exportCanvas.width = CnfThawFloat(100.0f, "ExportCanvas_Width"); - exportCanvas.height = CnfThawFloat(100.0f, "ExportCanvas_Height"); - exportCanvas.dx = CnfThawFloat( 5.0f, "ExportCanvas_Dx"); - exportCanvas.dy = CnfThawFloat( 5.0f, "ExportCanvas_Dy"); + exportCanvas.width = settings->ThawFloat("ExportCanvas_Width", 100.0); + exportCanvas.height = settings->ThawFloat("ExportCanvas_Height", 100.0); + exportCanvas.dx = settings->ThawFloat("ExportCanvas_Dx", 5.0); + exportCanvas.dy = settings->ThawFloat("ExportCanvas_Dy", 5.0); // Extra parameters when exporting G code - gCode.depth = CnfThawFloat(10.0f, "GCode_Depth"); - gCode.passes = CnfThawInt(1, "GCode_Passes"); - gCode.feed = CnfThawFloat(10.0f, "GCode_Feed"); - gCode.plungeFeed = CnfThawFloat(10.0f, "GCode_PlungeFeed"); + gCode.depth = settings->ThawFloat("GCode_Depth", 10.0); + gCode.passes = settings->ThawInt("GCode_Passes", 1); + gCode.feed = settings->ThawFloat("GCode_Feed", 10.0); + gCode.plungeFeed = settings->ThawFloat("GCode_PlungeFeed", 10.0); // Show toolbar in the graphics window - showToolbar = CnfThawBool(true, "ShowToolbar"); + showToolbar = settings->ThawBool("ShowToolbar", true); // Recent files menus - for(i = 0; i < MAX_RECENT; i++) { - RecentFile[i] = CnfThawString("", "RecentFile_" + std::to_string(i)); + for(size_t i = 0; i < MAX_RECENT; i++) { + std::string rawPath = settings->ThawString("RecentFile_" + std::to_string(i), ""); + if(rawPath.empty()) continue; + recentFiles.push_back(Platform::Path::From(rawPath)); } - RefreshRecentMenus(); // Autosave timer - autosaveInterval = CnfThawInt(5, "AutosaveInterval"); + autosaveInterval = settings->ThawInt("AutosaveInterval", 5); + // Locale + std::string locale = settings->ThawString("Locale", ""); + if(!locale.empty()) { + SetLocale(locale); + } + + generateAllTimer = Platform::CreateTimer(); + generateAllTimer->onTimeout = std::bind(&SolveSpaceUI::GenerateAll, &SS, Generate::DIRTY, + /*andFindFree=*/false, /*genForBBox=*/false); + + showTWTimer = Platform::CreateTimer(); + showTWTimer->onTimeout = std::bind(&TextWindow::Show, &TW); + + autosaveTimer = Platform::CreateTimer(); + autosaveTimer->onTimeout = std::bind(&SolveSpaceUI::Autosave, &SS); // The default styles (colors, line widths, etc.) are also stored in the // configuration file, but we will automatically load those as we need // them. - SetAutosaveTimerFor(autosaveInterval); + ScheduleAutosave(); NewFile(); AfterNewFile(); + + if(TW.window && GW.window) { + TW.window->ThawPosition(settings, "TextWindow"); + GW.window->ThawPosition(settings, "GraphicsWindow"); + TW.window->SetVisible(true); + GW.window->SetVisible(true); + GW.window->Focus(); + + // Do this once the window is created. + Request3DConnexionEventsForWindow(GW.window); + } } -bool SolveSpaceUI::LoadAutosaveFor(const std::string &filename) { - std::string autosaveFile = filename + AUTOSAVE_SUFFIX; +bool SolveSpaceUI::LoadAutosaveFor(const Platform::Path &filename) { + Platform::Path autosaveFile = filename.WithExtension(BACKUP_EXT); - FILE *f = ssfopen(autosaveFile, "rb"); + FILE *f = OpenFile(autosaveFile, "rb"); if(!f) return false; fclose(f); - if(LoadAutosaveYesNo() == DIALOG_YES) { + Platform::MessageDialogRef dialog = CreateMessageDialog(GW.window); + + using Platform::MessageDialog; + dialog->SetType(MessageDialog::Type::QUESTION); + dialog->SetTitle(C_("title", "Autosave Available")); + dialog->SetMessage(C_("dialog", "An autosave file is available for this sketch.")); + dialog->SetDescription(C_("dialog", "Do you want to load the autosave file instead?")); + dialog->AddButton(C_("button", "&Load autosave"), MessageDialog::Response::YES, + /*isDefault=*/true); + dialog->AddButton(C_("button", "Do&n't Load"), MessageDialog::Response::NO); + + // FIXME(async): asyncify this call + if(dialog->RunModal() == MessageDialog::Response::YES) { unsaved = true; - return LoadFromFile(autosaveFile); + return LoadFromFile(autosaveFile, /*canCancel=*/true); } return false; } -bool SolveSpaceUI::OpenFile(const std::string &filename) { +bool SolveSpaceUI::Load(const Platform::Path &filename) { bool autosaveLoaded = LoadAutosaveFor(filename); - bool fileLoaded = autosaveLoaded || LoadFromFile(filename); - if(fileLoaded) + bool fileLoaded = autosaveLoaded || LoadFromFile(filename, /*canCancel=*/true); + if(fileLoaded) { saveFile = filename; - bool success = fileLoaded && ReloadAllImported(/*canCancel=*/true); - if(success) { AddToRecentList(filename); } else { - saveFile = ""; + saveFile.Clear(); NewFile(); } AfterNewFile(); unsaved = autosaveLoaded; - return success; + return fileLoaded; } -void SolveSpaceUI::Exit(void) { +void SolveSpaceUI::Exit() { + Platform::SettingsRef settings = Platform::GetSettings(); + + GW.window->FreezePosition(settings, "GraphicsWindow"); + TW.window->FreezePosition(settings, "TextWindow"); + // Recent files - for(int i = 0; i < MAX_RECENT; i++) - CnfFreezeString(RecentFile[i], "RecentFile_" + std::to_string(i)); + for(size_t i = 0; i < MAX_RECENT; i++) { + std::string rawPath; + if(recentFiles.size() > i) { + rawPath = recentFiles[i].raw; + } + settings->FreezeString("RecentFile_" + std::to_string(i), rawPath); + } // Model colors - for(int i = 0; i < MODEL_COLORS; i++) - CnfFreezeColor(modelColor[i], "ModelColor_" + std::to_string(i)); + for(size_t i = 0; i < MODEL_COLORS; i++) + settings->FreezeColor("ModelColor_" + std::to_string(i), modelColor[i]); // Light intensities - CnfFreezeFloat((float)lightIntensity[0], "LightIntensity_0"); - CnfFreezeFloat((float)lightIntensity[1], "LightIntensity_1"); + settings->FreezeFloat("LightIntensity_0", (float)lightIntensity[0]); + settings->FreezeFloat("LightIntensity_1", (float)lightIntensity[1]); + settings->FreezeFloat("Light_Ambient", (float)ambientIntensity); // Light directions - CnfFreezeFloat((float)lightDir[0].x, "LightDir_0_Right"); - CnfFreezeFloat((float)lightDir[0].y, "LightDir_0_Up"); - CnfFreezeFloat((float)lightDir[0].z, "LightDir_0_Forward"); - CnfFreezeFloat((float)lightDir[1].x, "LightDir_1_Right"); - CnfFreezeFloat((float)lightDir[1].y, "LightDir_1_Up"); - CnfFreezeFloat((float)lightDir[1].z, "LightDir_1_Forward"); + settings->FreezeFloat("LightDir_0_Right", (float)lightDir[0].x); + settings->FreezeFloat("LightDir_0_Up", (float)lightDir[0].y); + settings->FreezeFloat("LightDir_0_Forward", (float)lightDir[0].z); + settings->FreezeFloat("LightDir_1_Right", (float)lightDir[1].x); + settings->FreezeFloat("LightDir_1_Up", (float)lightDir[1].y); + settings->FreezeFloat("LightDir_1_Forward", (float)lightDir[1].z); // Chord tolerance - CnfFreezeFloat((float)chordTol, "ChordTolerancePct"); + settings->FreezeFloat("ChordTolerancePct", (float)chordTol); // Max pwl segments to generate - CnfFreezeInt((uint32_t)maxSegments, "MaxSegments"); + settings->FreezeInt("MaxSegments", (uint32_t)maxSegments); // Export Chord tolerance - CnfFreezeFloat((float)exportChordTol, "ExportChordTolerance"); + settings->FreezeFloat("ExportChordTolerance", (float)exportChordTol); // Export Max pwl segments to generate - CnfFreezeInt((uint32_t)exportMaxSegments, "ExportMaxSegments"); + settings->FreezeInt("ExportMaxSegments", (uint32_t)exportMaxSegments); + // Timeout for finding which constraints to fix Jacobian + settings->FreezeInt("TimeoutRedundantConstraints", (uint32_t)timeoutRedundantConstr); // View units - CnfFreezeInt((uint32_t)viewUnits, "ViewUnits"); + settings->FreezeInt("ViewUnits", (uint32_t)viewUnits); // Number of digits after the decimal point - CnfFreezeInt((uint32_t)afterDecimalMm, "AfterDecimalMm"); - CnfFreezeInt((uint32_t)afterDecimalInch, "AfterDecimalInch"); + settings->FreezeInt("AfterDecimalMm", (uint32_t)afterDecimalMm); + settings->FreezeInt("AfterDecimalInch", (uint32_t)afterDecimalInch); + settings->FreezeInt("AfterDecimalDegree", (uint32_t)afterDecimalDegree); + settings->FreezeBool("UseSIPrefixes", useSIPrefixes); // Camera tangent (determines perspective) - CnfFreezeFloat((float)cameraTangent, "CameraTangent"); + settings->FreezeFloat("CameraTangent", (float)cameraTangent); // Grid spacing - CnfFreezeFloat(gridSpacing, "GridSpacing"); + settings->FreezeFloat("GridSpacing", gridSpacing); // Export scale - CnfFreezeFloat(exportScale, "ExportScale"); + settings->FreezeFloat("ExportScale", exportScale); // Export offset (cutter radius comp) - CnfFreezeFloat(exportOffset, "ExportOffset"); + settings->FreezeFloat("ExportOffset", exportOffset); // Rewrite exported colors close to white into black (assuming white bg) - CnfFreezeBool(fixExportColors, "FixExportColors"); + settings->FreezeBool("FixExportColors", fixExportColors); + // Export background color + settings->FreezeBool("ExportBackgroundColor", exportBackgroundColor); // Draw back faces of triangles (when mesh is leaky/self-intersecting) - CnfFreezeBool(drawBackFaces, "DrawBackFaces"); + settings->FreezeBool("DrawBackFaces", drawBackFaces); + // Draw closed polygons areas + settings->FreezeBool("ShowContourAreas", showContourAreas); // Check that contours are closed and not self-intersecting - CnfFreezeBool(checkClosedContour, "CheckClosedContour"); + settings->FreezeBool("CheckClosedContour", checkClosedContour); + // Use turntable mouse navigation + settings->FreezeBool("TurntableNav", turntableNav); + // Immediately edit dimensions + settings->FreezeBool("ImmediatelyEditDimension", immediatelyEditDimension); + // Enable automatic constrains for lines + settings->FreezeBool("AutomaticLineConstraints", automaticLineConstraints); // Export shaded triangles in a 2d view - CnfFreezeBool(exportShadedTriangles, "ExportShadedTriangles"); + settings->FreezeBool("ExportShadedTriangles", exportShadedTriangles); // Export pwl curves (instead of exact) always - CnfFreezeBool(exportPwlCurves, "ExportPwlCurves"); + settings->FreezeBool("ExportPwlCurves", exportPwlCurves); // Background color on-screen - CnfFreezeColor(backgroundColor, "BackgroundColor"); + settings->FreezeColor("BackgroundColor", backgroundColor); // Whether export canvas size is fixed or derived from bbox - CnfFreezeBool(exportCanvasSizeAuto, "ExportCanvasSizeAuto"); + settings->FreezeBool("ExportCanvasSizeAuto", exportCanvasSizeAuto); // Margins for automatic canvas size - CnfFreezeFloat(exportMargin.left, "ExportMargin_Left"); - CnfFreezeFloat(exportMargin.right, "ExportMargin_Right"); - CnfFreezeFloat(exportMargin.bottom, "ExportMargin_Bottom"); - CnfFreezeFloat(exportMargin.top, "ExportMargin_Top"); + settings->FreezeFloat("ExportMargin_Left", exportMargin.left); + settings->FreezeFloat("ExportMargin_Right", exportMargin.right); + settings->FreezeFloat("ExportMargin_Bottom", exportMargin.bottom); + settings->FreezeFloat("ExportMargin_Top", exportMargin.top); // Dimensions for fixed canvas size - CnfFreezeFloat(exportCanvas.width, "ExportCanvas_Width"); - CnfFreezeFloat(exportCanvas.height, "ExportCanvas_Height"); - CnfFreezeFloat(exportCanvas.dx, "ExportCanvas_Dx"); - CnfFreezeFloat(exportCanvas.dy, "ExportCanvas_Dy"); + settings->FreezeFloat("ExportCanvas_Width", exportCanvas.width); + settings->FreezeFloat("ExportCanvas_Height", exportCanvas.height); + settings->FreezeFloat("ExportCanvas_Dx", exportCanvas.dx); + settings->FreezeFloat("ExportCanvas_Dy", exportCanvas.dy); // Extra parameters when exporting G code - CnfFreezeFloat(gCode.depth, "GCode_Depth"); - CnfFreezeInt(gCode.passes, "GCode_Passes"); - CnfFreezeFloat(gCode.feed, "GCode_Feed"); - CnfFreezeFloat(gCode.plungeFeed, "GCode_PlungeFeed"); + settings->FreezeFloat("GCode_Depth", gCode.depth); + settings->FreezeInt("GCode_Passes", gCode.passes); + settings->FreezeFloat("GCode_Feed", gCode.feed); + settings->FreezeFloat("GCode_PlungeFeed", gCode.plungeFeed); // Show toolbar in the graphics window - CnfFreezeBool(showToolbar, "ShowToolbar"); + settings->FreezeBool("ShowToolbar", showToolbar); // Autosave timer - CnfFreezeInt(autosaveInterval, "AutosaveInterval"); + settings->FreezeInt("AutosaveInterval", autosaveInterval); // And the default styles, colors and line widths and such. - Style::FreezeDefaultStyles(); + Style::FreezeDefaultStyles(settings); - ExitNow(); + Platform::ExitGui(); } void SolveSpaceUI::ScheduleGenerateAll() { - if(!later.scheduled) ScheduleLater(); - later.scheduled = true; - later.generateAll = true; + generateAllTimer->RunAfterProcessingEvents(); } void SolveSpaceUI::ScheduleShowTW() { - if(!later.scheduled) ScheduleLater(); - later.scheduled = true; - later.showTW = true; + showTWTimer->RunAfterProcessingEvents(); } -void SolveSpaceUI::DoLater(void) { - if(later.generateAll) GenerateAll(); - if(later.showTW) TW.Show(); - later = {}; +void SolveSpaceUI::ScheduleAutosave() { + autosaveTimer->RunAfter(autosaveInterval * 60 * 1000); } -double SolveSpaceUI::MmPerUnit(void) { - if(viewUnits == UNIT_INCHES) { - return 25.4; - } else { - return 1.0; +double SolveSpaceUI::MmPerUnit() { + switch(viewUnits) { + case Unit::INCHES: return 25.4; + case Unit::METERS: return 1000.0; + case Unit::MM: return 1.0; } + return 1.0; } -const char *SolveSpaceUI::UnitName(void) { - if(viewUnits == UNIT_INCHES) { - return "inch"; - } else { - return "mm"; +const char *SolveSpaceUI::UnitName() { + switch(viewUnits) { + case Unit::INCHES: return "in"; + case Unit::METERS: return "m"; + case Unit::MM: return "mm"; } + return ""; } + std::string SolveSpaceUI::MmToString(double v) { - if(viewUnits == UNIT_INCHES) { - return ssprintf("%.*f", afterDecimalInch, v/25.4); + v /= MmPerUnit(); + return ssprintf("%.*f", UnitDigitsAfterDecimal(), v); +} +static const char *DimToString(int dim) { + switch(dim) { + case 3: return "³"; + case 2: return "²"; + case 1: return ""; + default: ssassert(false, "Unexpected dimension"); + } +} +static std::pair SelectSIPrefixMm(int deg) { + if(deg >= 3) return { 3, "km" }; + else if(deg >= 0) return { 0, "m" }; + else if(deg >= -2) return { -2, "cm" }; + else if(deg >= -3) return { -3, "mm" }; + else if(deg >= -6) return { -6, "µm" }; + else return { -9, "nm" }; +} +static std::pair SelectSIPrefixInch(int deg) { + if(deg >= 0) return { 0, "in" }; + else if(deg >= -3) return { -3, "mil" }; + else return { -6, "µin" }; +} +std::string SolveSpaceUI::MmToStringSI(double v, int dim) { + bool compact = false; + if(dim == 0) { + if(!useSIPrefixes) return MmToString(v); + compact = true; + dim = 1; + } + + v /= pow((viewUnits == Unit::INCHES) ? 25.4 : 1000, dim); + int vdeg = (int)((log10(fabs(v))) / dim); + std::string unit; + if(fabs(v) > 0.0) { + int sdeg = 0; + std::tie(sdeg, unit) = + (viewUnits == Unit::INCHES) + ? SelectSIPrefixInch(vdeg) + : SelectSIPrefixMm(vdeg); + v /= pow(10.0, sdeg * dim); + } + int pdeg = (int)ceil(log10(fabs(v) + 1e-10)); + return ssprintf("%.*g%s%s%s", pdeg + UnitDigitsAfterDecimal(), v, + compact ? "" : " ", unit.c_str(), DimToString(dim)); +} +std::string SolveSpaceUI::DegreeToString(double v) { + if(fabs(v - floor(v)) > 1e-10) { + return ssprintf("%.*f", afterDecimalDegree, v); } else { - return ssprintf("%.*f", afterDecimalMm, v); + return ssprintf("%.0f", v); } } double SolveSpaceUI::ExprToMm(Expr *e) { @@ -265,29 +390,29 @@ double SolveSpaceUI::ExprToMm(Expr *e) { double SolveSpaceUI::StringToMm(const std::string &str) { return std::stod(str) * MmPerUnit(); } -double SolveSpaceUI::ChordTolMm(void) { +double SolveSpaceUI::ChordTolMm() { if(exportMode) return ExportChordTolMm(); return chordTolCalculated; } -double SolveSpaceUI::ExportChordTolMm(void) { +double SolveSpaceUI::ExportChordTolMm() { return exportChordTol / exportScale; } -int SolveSpaceUI::GetMaxSegments(void) { +int SolveSpaceUI::GetMaxSegments() { if(exportMode) return exportMaxSegments; return maxSegments; } -int SolveSpaceUI::UnitDigitsAfterDecimal(void) { - return (viewUnits == UNIT_INCHES) ? afterDecimalInch : afterDecimalMm; +int SolveSpaceUI::UnitDigitsAfterDecimal() { + return (viewUnits == Unit::INCHES) ? afterDecimalInch : afterDecimalMm; } void SolveSpaceUI::SetUnitDigitsAfterDecimal(int v) { - if(viewUnits == UNIT_INCHES) { + if(viewUnits == Unit::INCHES) { afterDecimalInch = v; } else { afterDecimalMm = v; } } -double SolveSpaceUI::CameraTangent(void) { +double SolveSpaceUI::CameraTangent() { if(!usePerspectiveProj) { return 0; } else { @@ -295,7 +420,7 @@ double SolveSpaceUI::CameraTangent(void) { } } -void SolveSpaceUI::AfterNewFile(void) { +void SolveSpaceUI::AfterNewFile() { // Clear out the traced point, which is no longer valid traced.point = Entity::NO_ENTITY; traced.path.l.Clear(); @@ -304,6 +429,7 @@ void SolveSpaceUI::AfterNewFile(void) { // Quit export mode justExportedInfo.draw = false; + centerOfMass.draw = false; exportMode = false; // GenerateAll() expects the view to be valid, because it uses that to @@ -313,282 +439,296 @@ void SolveSpaceUI::AfterNewFile(void) { SS.GW.projRight = Vector::From(1, 0, 0); SS.GW.projUp = Vector::From(0, 1, 0); - GenerateAll(GENERATE_REGEN); + GenerateAll(Generate::ALL); - TW.Init(); GW.Init(); + TW.Init(); unsaved = false; - int w, h; - GetGraphicsWindowSize(&w, &h); - GW.width = w; - GW.height = h; - - // The triangles haven't been generated yet, but zoom to fit the entities - // roughly in the window, since that sets the mesh tolerance. Consider - // invisible entities, so we still get something reasonable if the only - // thing visible is the not-yet-generated surfaces. - GW.ZoomToFit(true); - - GenerateAll(GENERATE_ALL); - SS.ScheduleShowTW(); - // Then zoom to fit again, to fit the triangles - GW.ZoomToFit(false); + GW.ZoomToFit(); // Create all the default styles; they'll get created on the fly anyways, // but can't hurt to do it now. Style::CreateAllDefaultStyles(); - UpdateWindowTitle(); + UpdateWindowTitles(); } -void SolveSpaceUI::RemoveFromRecentList(const std::string &filename) { - int src, dest; - dest = 0; - for(src = 0; src < MAX_RECENT; src++) { - if(filename != RecentFile[src]) { - if(src != dest) RecentFile[dest] = RecentFile[src]; - dest++; - } +void SolveSpaceUI::AddToRecentList(const Platform::Path &filename) { + auto it = std::find_if(recentFiles.begin(), recentFiles.end(), + [&](const Platform::Path &p) { return p.Equals(filename); }); + if(it != recentFiles.end()) { + recentFiles.erase(it); } - while(dest < MAX_RECENT) RecentFile[dest++].clear(); - RefreshRecentMenus(); -} -void SolveSpaceUI::AddToRecentList(const std::string &filename) { - RemoveFromRecentList(filename); - int src; - for(src = MAX_RECENT - 2; src >= 0; src--) { - RecentFile[src+1] = RecentFile[src]; + if(recentFiles.size() > MAX_RECENT) { + recentFiles.erase(recentFiles.begin() + MAX_RECENT); } - RecentFile[0] = filename; - RefreshRecentMenus(); + + recentFiles.insert(recentFiles.begin(), filename); + GW.PopulateRecentFiles(); } bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) { - std::string prevSaveFile = saveFile; - - if(saveAs || saveFile.empty()) { - if(!GetSaveFile(&saveFile, "", SlvsFileFilter)) return false; - // need to get new filename directly into saveFile, since that - // determines linkFileRel path + Platform::SettingsRef settings = Platform::GetSettings(); + Platform::Path newSaveFile = saveFile; + + if(saveAs || saveFile.IsEmpty()) { + Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(GW.window); + dialog->AddFilter(C_("file-type", "SolveSpace models"), { SKETCH_EXT }); + dialog->ThawChoices(settings, "Sketch"); + if(!newSaveFile.IsEmpty()) { + dialog->SetFilename(newSaveFile); + } + if(dialog->RunModal()) { + dialog->FreezeChoices(settings, "Sketch"); + newSaveFile = dialog->GetFilename(); + } else { + return false; + } } - if(SaveToFile(saveFile)) { - AddToRecentList(saveFile); + if(SaveToFile(newSaveFile)) { + AddToRecentList(newSaveFile); RemoveAutosave(); + saveFile = newSaveFile; unsaved = false; return true; } else { - // don't store an invalid save filename - saveFile = prevSaveFile; return false; } } -bool SolveSpaceUI::Autosave() +void SolveSpaceUI::Autosave() { - SetAutosaveTimerFor(autosaveInterval); - - if(!saveFile.empty() && unsaved) - return SaveToFile(saveFile + AUTOSAVE_SUFFIX); + ScheduleAutosave(); - return false; + if(!saveFile.IsEmpty() && unsaved) { + SaveToFile(saveFile.WithExtension(BACKUP_EXT)); + } } void SolveSpaceUI::RemoveAutosave() { - std::string autosaveFile = saveFile + AUTOSAVE_SUFFIX; - ssremove(autosaveFile); + Platform::Path autosaveFile = saveFile.WithExtension(BACKUP_EXT); + RemoveFile(autosaveFile); } -bool SolveSpaceUI::OkayToStartNewFile(void) { +bool SolveSpaceUI::OkayToStartNewFile() { if(!unsaved) return true; - switch(SaveFileYesNoCancel()) { - case DIALOG_YES: - return GetFilenameAndSave(false); + Platform::MessageDialogRef dialog = CreateMessageDialog(GW.window); - case DIALOG_NO: + using Platform::MessageDialog; + dialog->SetType(MessageDialog::Type::QUESTION); + dialog->SetTitle(C_("title", "Modified File")); + if(!SolveSpace::SS.saveFile.IsEmpty()) { + dialog->SetMessage(ssprintf(C_("dialog", "Do you want to save the changes you made to " + "the sketch “%s”?"), saveFile.raw.c_str())); + } else { + dialog->SetMessage(C_("dialog", "Do you want to save the changes you made to " + "the new sketch?")); + } + dialog->SetDescription(C_("dialog", "Your changes will be lost if you don't save them.")); + dialog->AddButton(C_("button", "&Save"), MessageDialog::Response::YES, + /*isDefault=*/true); + dialog->AddButton(C_("button", "Do&n't Save"), MessageDialog::Response::NO); + dialog->AddButton(C_("button", "&Cancel"), MessageDialog::Response::CANCEL); + + // FIXME(async): asyncify this call + switch(dialog->RunModal()) { + case MessageDialog::Response::YES: + return GetFilenameAndSave(/*saveAs=*/false); + + case MessageDialog::Response::NO: RemoveAutosave(); return true; - case DIALOG_CANCEL: + default: return false; - - default: oops(); break; } } -void SolveSpaceUI::UpdateWindowTitle(void) { - SetCurrentFilename(saveFile); -} +void SolveSpaceUI::UpdateWindowTitles() { + if(!GW.window || !TW.window) return; -static std::string Extension(const std::string &filename) { - int dot = filename.rfind('.'); - if(dot >= 0) { - std::string ext = filename.substr(dot + 1, filename.length()); - std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); - return ext; + if(saveFile.IsEmpty()) { + GW.window->SetTitle(C_("title", "(new sketch)")); + } else { + if(!GW.window->SetTitleForFilename(saveFile)) { + GW.window->SetTitle(saveFile.raw); + } } - return ""; + + TW.window->SetTitle(C_("title", "Property Browser")); } -void SolveSpaceUI::MenuFile(int id) { - if(id >= RECENT_OPEN && id < (RECENT_OPEN+MAX_RECENT)) { - if(!SS.OkayToStartNewFile()) return; - - std::string newFile = RecentFile[id - RECENT_OPEN]; - SS.OpenFile(newFile); - return; - } +void SolveSpaceUI::MenuFile(Command id) { + Platform::SettingsRef settings = Platform::GetSettings(); switch(id) { - case GraphicsWindow::MNU_NEW: + case Command::NEW: if(!SS.OkayToStartNewFile()) break; - SS.saveFile = ""; + SS.saveFile.Clear(); SS.NewFile(); SS.AfterNewFile(); break; - case GraphicsWindow::MNU_OPEN: { + case Command::OPEN: { if(!SS.OkayToStartNewFile()) break; - std::string newFile; - if(GetOpenFile(&newFile, "", SlvsFileFilter)) { - SS.OpenFile(newFile); + Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window); + dialog->AddFilters(Platform::SolveSpaceModelFileFilters); + dialog->ThawChoices(settings, "Sketch"); + if(dialog->RunModal()) { + dialog->FreezeChoices(settings, "Sketch"); + SS.Load(dialog->GetFilename()); } break; } - case GraphicsWindow::MNU_SAVE: - SS.GetFilenameAndSave(false); + case Command::SAVE: + SS.GetFilenameAndSave(/*saveAs=*/false); break; - case GraphicsWindow::MNU_SAVE_AS: - SS.GetFilenameAndSave(true); + case Command::SAVE_AS: + SS.GetFilenameAndSave(/*saveAs=*/true); break; - case GraphicsWindow::MNU_EXPORT_PNG: { - std::string exportFile; - if(!GetSaveFile(&exportFile, "", PngFileFilter)) break; - SS.ExportAsPngTo(exportFile); + case Command::EXPORT_IMAGE: { + Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); + dialog->AddFilters(Platform::RasterFileFilters); + dialog->ThawChoices(settings, "ExportImage"); + if(dialog->RunModal()) { + dialog->FreezeChoices(settings, "ExportImage"); + SS.ExportAsPngTo(dialog->GetFilename()); + } break; } - case GraphicsWindow::MNU_EXPORT_VIEW: { - std::string exportFile; - if(!GetSaveFile(&exportFile, CnfThawString("", "ViewExportFormat"), - VectorFileFilter)) break; - CnfFreezeString(Extension(exportFile), "ViewExportFormat"); + case Command::EXPORT_VIEW: { + Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); + dialog->AddFilters(Platform::VectorFileFilters); + dialog->ThawChoices(settings, "ExportView"); + if(!dialog->RunModal()) break; + dialog->FreezeChoices(settings, "ExportView"); // If the user is exporting something where it would be // inappropriate to include the constraints, then warn. if(SS.GW.showConstraints && - (FilenameHasExtension(exportFile, ".txt") || + (dialog->GetFilename().HasExtension("txt") || fabs(SS.exportOffset) > LENGTH_EPS)) { - Message("Constraints are currently shown, and will be exported " - "in the toolpath. This is probably not what you want; " - "hide them by clicking the link at the top of the " - "text window."); + Message(_("Constraints are currently shown, and will be exported " + "in the toolpath. This is probably not what you want; " + "hide them by clicking the link at the top of the " + "text window.")); } - SS.ExportViewOrWireframeTo(exportFile, false); + SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe=*/false); break; } - case GraphicsWindow::MNU_EXPORT_WIREFRAME: { - std::string exportFile; - if(!GetSaveFile(&exportFile, CnfThawString("", "WireframeExportFormat"), - Vector3dFileFilter)) break; - CnfFreezeString(Extension(exportFile), "WireframeExportFormat"); + case Command::EXPORT_WIREFRAME: { + Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); + dialog->AddFilters(Platform::Vector3dFileFilters); + dialog->ThawChoices(settings, "ExportWireframe"); + if(!dialog->RunModal()) break; + dialog->FreezeChoices(settings, "ExportWireframe"); - SS.ExportViewOrWireframeTo(exportFile, true); + SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe*/true); break; } - case GraphicsWindow::MNU_EXPORT_SECTION: { - std::string exportFile; - if(!GetSaveFile(&exportFile, CnfThawString("", "SectionExportFormat"), - VectorFileFilter)) break; - CnfFreezeString(Extension(exportFile), "SectionExportFormat"); + case Command::EXPORT_SECTION: { + Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); + dialog->AddFilters(Platform::VectorFileFilters); + dialog->ThawChoices(settings, "ExportSection"); + if(!dialog->RunModal()) break; + dialog->FreezeChoices(settings, "ExportSection"); - SS.ExportSectionTo(exportFile); + SS.ExportSectionTo(dialog->GetFilename()); break; } - case GraphicsWindow::MNU_EXPORT_MESH: { - std::string exportFile; - if(!GetSaveFile(&exportFile, CnfThawString("", "MeshExportFormat"), - MeshFileFilter)) break; - CnfFreezeString(Extension(exportFile), "MeshExportFormat"); + case Command::EXPORT_MESH: { + Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); + dialog->AddFilters(Platform::MeshFileFilters); + dialog->ThawChoices(settings, "ExportMesh"); + if(!dialog->RunModal()) break; + dialog->FreezeChoices(settings, "ExportMesh"); - SS.ExportMeshTo(exportFile); + SS.ExportMeshTo(dialog->GetFilename()); break; } - case GraphicsWindow::MNU_EXPORT_SURFACES: { - std::string exportFile; - if(!GetSaveFile(&exportFile, CnfThawString("", "SurfacesExportFormat"), - SurfaceFileFilter)) break; - CnfFreezeString(Extension(exportFile), "SurfacesExportFormat"); + case Command::EXPORT_SURFACES: { + Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); + dialog->AddFilters(Platform::SurfaceFileFilters); + dialog->ThawChoices(settings, "ExportSurfaces"); + if(!dialog->RunModal()) break; + dialog->FreezeChoices(settings, "ExportSurfaces"); StepFileWriter sfw = {}; - sfw.ExportSurfacesTo(exportFile); + sfw.ExportSurfacesTo(dialog->GetFilename()); break; } - case GraphicsWindow::MNU_IMPORT: { - std::string importFile; - if(!GetOpenFile(&importFile, CnfThawString("", "ImportFormat"), - ImportableFileFilter)) break; - CnfFreezeString(Extension(importFile), "ImportFormat"); + case Command::IMPORT: { + Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window); + dialog->AddFilters(Platform::ImportFileFilters); + dialog->ThawChoices(settings, "Import"); + if(!dialog->RunModal()) break; + dialog->FreezeChoices(settings, "Import"); - if(Extension(importFile) == "dxf") { + Platform::Path importFile = dialog->GetFilename(); + if(importFile.HasExtension("dxf")) { ImportDxf(importFile); - } else if(Extension(importFile) == "dwg") { + } else if(importFile.HasExtension("dwg")) { ImportDwg(importFile); } else { - Error("Can't identify file type from file extension of " - "filename '%s'; try .dxf or .dwg.", importFile.c_str()); + Error(_("Can't identify file type from file extension of " + "filename '%s'; try .dxf or .dwg."), importFile.raw.c_str()); + break; } - SS.GenerateAll(SolveSpaceUI::GENERATE_UNTIL_ACTIVE); + SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE); SS.ScheduleShowTW(); break; } - case GraphicsWindow::MNU_EXIT: + case Command::EXIT: if(!SS.OkayToStartNewFile()) break; SS.Exit(); break; - default: oops(); + default: ssassert(false, "Unexpected menu ID"); } - SS.UpdateWindowTitle(); + SS.UpdateWindowTitles(); } -void SolveSpaceUI::MenuAnalyze(int id) { +void SolveSpaceUI::MenuAnalyze(Command id) { + Platform::SettingsRef settings = Platform::GetSettings(); + SS.GW.GroupSelection(); -#define gs (SS.GW.gs) + auto const &gs = SS.GW.gs; switch(id) { - case GraphicsWindow::MNU_STEP_DIM: + case Command::STEP_DIM: if(gs.constraints == 1 && gs.n == 0) { Constraint *c = SK.GetConstraint(gs.constraint[0]); if(c->HasLabel() && !c->reference) { - SS.TW.shown.dimFinish = c->valA; - SS.TW.shown.dimSteps = 10; - SS.TW.shown.dimIsDistance = - (c->type != Constraint::ANGLE) && - (c->type != Constraint::LENGTH_RATIO) && - (c->type != Constraint::LENGTH_DIFFERENCE); + SS.TW.stepDim.finish = c->valA; + SS.TW.stepDim.steps = 10; + SS.TW.stepDim.isDistance = + (c->type != Constraint::Type::ANGLE) && + (c->type != Constraint::Type::LENGTH_RATIO) && + (c->type != Constraint::Type::LENGTH_DIFFERENCE); SS.TW.shown.constraint = c->h; - SS.TW.shown.screen = TextWindow::SCREEN_STEP_DIMENSION; + SS.TW.shown.screen = TextWindow::Screen::STEP_DIMENSION; // The step params are specified in the text window, // so force that to be shown. @@ -597,215 +737,223 @@ void SolveSpaceUI::MenuAnalyze(int id) { SS.ScheduleShowTW(); SS.GW.ClearSelection(); } else { - Error("Constraint must have a label, and must not be " - "a reference dimension."); + Error(_("Constraint must have a label, and must not be " + "a reference dimension.")); } } else { - Error("Bad selection for step dimension; select a constraint."); + Error(_("Bad selection for step dimension; select a constraint.")); } break; - case GraphicsWindow::MNU_NAKED_EDGES: { - SS.nakedEdges.Clear(); - - Group *g = SK.GetGroup(SS.GW.activeGroup); - SMesh *m = &(g->displayMesh); - SKdNode *root = SKdNode::From(m); - bool inters, leaks; - root->MakeCertainEdgesInto(&(SS.nakedEdges), - SKdNode::NAKED_OR_SELF_INTER_EDGES, true, &inters, &leaks); - - InvalidateGraphics(); - - const char *intersMsg = inters ? - "The mesh is self-intersecting (NOT okay, invalid)." : - "The mesh is not self-intersecting (okay, valid)."; - const char *leaksMsg = leaks ? - "The mesh has naked edges (NOT okay, invalid)." : - "The mesh is watertight (okay, valid)."; - - std::string cntMsg = ssprintf("\n\nThe model contains %d triangles, from " - "%d surfaces.", g->displayMesh.l.n, g->runningShell.surface.n); - - if(SS.nakedEdges.l.n == 0) { - Message("%s\n\n%s\n\nZero problematic edges, good.%s", - intersMsg, leaksMsg, cntMsg.c_str()); - } else { - Error("%s\n\n%s\n\n%d problematic edges, bad.%s", - intersMsg, leaksMsg, SS.nakedEdges.l.n, cntMsg.c_str()); - } + case Command::NAKED_EDGES: { + ShowNakedEdges(/*reportOnlyWhenNotOkay=*/false); break; } - case GraphicsWindow::MNU_INTERFERENCE: { + case Command::INTERFERENCE: { SS.nakedEdges.Clear(); SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); SKdNode *root = SKdNode::From(m); bool inters, leaks; root->MakeCertainEdgesInto(&(SS.nakedEdges), - SKdNode::SELF_INTER_EDGES, false, &inters, &leaks); + EdgeKind::SELF_INTER, /*coplanarIsInter=*/false, &inters, &leaks); - InvalidateGraphics(); + SS.GW.Invalidate(); if(inters) { Error("%d edges interfere with other triangles, bad.", SS.nakedEdges.l.n); } else { - Message("The assembly does not interfere, good."); + Message(_("The assembly does not interfere, good.")); } break; } - case GraphicsWindow::MNU_VOLUME: { - SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); + case Command::CENTER_OF_MASS: { + SS.UpdateCenterOfMass(); + SS.centerOfMass.draw = true; + SS.GW.Invalidate(); + break; + } - double vol = 0; - int i; - for(i = 0; i < m->l.n; i++) { - STriangle tr = m->l.elem[i]; - - // Translate to place vertex A at (x, y, 0) - Vector trans = Vector::From(tr.a.x, tr.a.y, 0); - tr.a = (tr.a).Minus(trans); - tr.b = (tr.b).Minus(trans); - tr.c = (tr.c).Minus(trans); - - // Rotate to place vertex B on the y-axis. Depending on - // whether the triangle is CW or CCW, C is either to the - // right or to the left of the y-axis. This handles the - // sign of our normal. - Vector u = Vector::From(-tr.b.y, tr.b.x, 0); - u = u.WithMagnitude(1); - Vector v = Vector::From(tr.b.x, tr.b.y, 0); - v = v.WithMagnitude(1); - Vector n = Vector::From(0, 0, 1); - - tr.a = (tr.a).DotInToCsys(u, v, n); - tr.b = (tr.b).DotInToCsys(u, v, n); - tr.c = (tr.c).DotInToCsys(u, v, n); - - n = tr.Normal().WithMagnitude(1); - - // Triangles on edge don't contribute - if(fabs(n.z) < LENGTH_EPS) continue; - - // The plane has equation p dot n = a dot n - double d = (tr.a).Dot(n); - // nx*x + ny*y + nz*z = d - // nz*z = d - nx*x - ny*y - double A = -n.x/n.z, B = -n.y/n.z, C = d/n.z; - - double mac = tr.c.y/tr.c.x, mbc = (tr.c.y - tr.b.y)/tr.c.x; - double xc = tr.c.x, yb = tr.b.y; - - // I asked Maple for - // int(int(A*x + B*y +C, y=mac*x..(mbc*x + yb)), x=0..xc); - double integral = - (1.0/3)*( - A*(mbc-mac)+ - (1.0/2)*B*(mbc*mbc-mac*mac) - )*(xc*xc*xc)+ - (1.0/2)*(A*yb+B*yb*mbc+C*(mbc-mac))*xc*xc+ - C*yb*xc+ - (1.0/2)*B*yb*yb*xc; - - vol += integral; + case Command::VOLUME: { + Group *g = SK.GetGroup(SS.GW.activeGroup); + double totalVol = g->displayMesh.CalculateVolume(); + std::string msg = ssprintf( + _("The volume of the solid model is:\n\n" + " %s"), + SS.MmToStringSI(totalVol, /*dim=*/3).c_str()); + + SMesh curMesh = {}; + g->thisShell.TriangulateInto(&curMesh); + double curVol = curMesh.CalculateVolume(); + if(curVol > 0.0) { + msg += ssprintf( + _("\nThe volume of current group mesh is:\n\n" + " %s"), + SS.MmToStringSI(curVol, /*dim=*/3).c_str()); } - std::string msg = ssprintf("The volume of the solid model is:\n\n"" %.3f %s^3", - vol / pow(SS.MmPerUnit(), 3), - SS.UnitName()); - - if(SS.viewUnits == SolveSpaceUI::UNIT_MM) { - msg += ssprintf("\n %.2f mL", vol/(10*10*10)); - } - msg += "\n\nCurved surfaces have been approximated as triangles.\n" - "This introduces error, typically of around 1%."; + msg += _("\n\nCurved surfaces have been approximated as triangles.\n" + "This introduces error, typically of around 1%."); Message("%s", msg.c_str()); break; } - case GraphicsWindow::MNU_AREA: { + case Command::AREA: { Group *g = SK.GetGroup(SS.GW.activeGroup); - if(g->polyError.how != Group::POLY_GOOD) { - Error("This group does not contain a correctly-formed " - "2d closed area. It is open, not coplanar, or self-" - "intersecting."); + SS.GW.GroupSelection(); + + if(gs.faces > 0) { + std::vector faces; + faces.push_back(gs.face[0].v); + if(gs.faces > 1) faces.push_back(gs.face[1].v); + double area = g->displayMesh.CalculateSurfaceArea(faces); + Message(_("The surface area of the selected faces is:\n\n" + " %s\n\n" + "Curves have been approximated as piecewise linear.\n" + "This introduces error, typically of around 1%%."), + SS.MmToStringSI(area, /*dim=*/2).c_str()); + break; + } + + if(g->polyError.how != PolyError::GOOD) { + Error(_("This group does not contain a correctly-formed " + "2d closed area. It is open, not coplanar, or self-" + "intersecting.")); break; } SEdgeList sel = {}; g->polyLoops.MakeEdgesInto(&sel); SPolygon sp = {}; - sel.AssemblePolygon(&sp, NULL, true); + sel.AssemblePolygon(&sp, NULL, /*keepDir=*/true); sp.normal = sp.ComputeNormal(); sp.FixContourDirections(); double area = sp.SignedArea(); - double scale = SS.MmPerUnit(); - Message("The area of the region sketched in this group is:\n\n" - " %.3f %s^2\n\n" - "Curves have been approximated as piecewise linear.\n" - "This introduces error, typically of around 1%%.", - area / (scale*scale), - SS.UnitName()); + Message(_("The area of the region sketched in this group is:\n\n" + " %s\n\n" + "Curves have been approximated as piecewise linear.\n" + "This introduces error, typically of around 1%%."), + SS.MmToStringSI(area, /*dim=*/2).c_str()); sel.Clear(); sp.Clear(); break; } - case GraphicsWindow::MNU_SHOW_DOF: + case Command::PERIMETER: { + if(gs.n > 0 && gs.n == gs.entities) { + double perimeter = 0.0; + for(int i = 0; i < gs.entities; i++) { + Entity *en = SK.entity.FindById(gs.entity[i]); + SEdgeList *el = en->GetOrGenerateEdges(); + for(const SEdge &e : el->l) { + perimeter += e.b.Minus(e.a).Magnitude(); + } + } + Message(_("The total length of the selected entities is:\n\n" + " %s\n\n" + "Curves have been approximated as piecewise linear.\n" + "This introduces error, typically of around 1%%."), + SS.MmToStringSI(perimeter, /*dim=*/1).c_str()); + } else { + Error(_("Bad selection for perimeter; select line segments, arcs, and curves.")); + } + break; + } + + case Command::SHOW_DOF: // This works like a normal solve, except that it calculates // which variables are free/bound at the same time. - SS.GenerateAll(SolveSpaceUI::GENERATE_ALL, true); + SS.GenerateAll(SolveSpaceUI::Generate::ALL, /*andFindFree=*/true); break; - case GraphicsWindow::MNU_TRACE_PT: + case Command::TRACE_PT: if(gs.points == 1 && gs.n == 1) { SS.traced.point = gs.point[0]; SS.GW.ClearSelection(); } else { - Error("Bad selection for trace; select a single point."); + Error(_("Bad selection for trace; select a single point.")); } break; - case GraphicsWindow::MNU_STOP_TRACING: { - std::string exportFile; - if(GetSaveFile(&exportFile, "", CsvFileFilter)) { - FILE *f = ssfopen(exportFile, "wb"); + case Command::STOP_TRACING: { + Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window); + dialog->AddFilters(Platform::CsvFileFilters); + dialog->ThawChoices(settings, "Trace"); + if(dialog->RunModal()) { + dialog->FreezeChoices(settings, "Trace"); + + FILE *f = OpenFile(dialog->GetFilename(), "wb"); if(f) { int i; SContour *sc = &(SS.traced.path); for(i = 0; i < sc->l.n; i++) { - Vector p = sc->l.elem[i].p; + Vector p = sc->l[i].p; double s = SS.exportScale; fprintf(f, "%.10f, %.10f, %.10f\r\n", p.x/s, p.y/s, p.z/s); } fclose(f); } else { - Error("Couldn't write to '%s'", exportFile.c_str()); + Error(_("Couldn't write to '%s'"), dialog->GetFilename().raw.c_str()); } } // Clear the trace, and stop tracing SS.traced.point = Entity::NO_ENTITY; SS.traced.path.l.Clear(); - InvalidateGraphics(); + SS.GW.Invalidate(); break; } - default: oops(); + default: ssassert(false, "Unexpected menu ID"); + } +} + +void SolveSpaceUI::ShowNakedEdges(bool reportOnlyWhenNotOkay) { + SS.nakedEdges.Clear(); + + Group *g = SK.GetGroup(SS.GW.activeGroup); + SMesh *m = &(g->displayMesh); + SKdNode *root = SKdNode::From(m); + bool inters, leaks; + root->MakeCertainEdgesInto(&(SS.nakedEdges), + EdgeKind::NAKED_OR_SELF_INTER, /*coplanarIsInter=*/true, &inters, &leaks); + + if(reportOnlyWhenNotOkay && !inters && !leaks && SS.nakedEdges.l.IsEmpty()) { + return; + } + SS.GW.Invalidate(); + + const char *intersMsg = inters ? + _("The mesh is self-intersecting (NOT okay, invalid).") : + _("The mesh is not self-intersecting (okay, valid)."); + const char *leaksMsg = leaks ? + _("The mesh has naked edges (NOT okay, invalid).") : + _("The mesh is watertight (okay, valid)."); + + std::string cntMsg = ssprintf( + _("\n\nThe model contains %d triangles, from %d surfaces."), + g->displayMesh.l.n, g->runningShell.surface.n); + + if(SS.nakedEdges.l.IsEmpty()) { + Message(_("%s\n\n%s\n\nZero problematic edges, good.%s"), + intersMsg, leaksMsg, cntMsg.c_str()); + } else { + Error(_("%s\n\n%s\n\n%d problematic edges, bad.%s"), + intersMsg, leaksMsg, SS.nakedEdges.l.n, cntMsg.c_str()); } } -void SolveSpaceUI::MenuHelp(int id) { +void SolveSpaceUI::MenuHelp(Command id) { switch(id) { - case GraphicsWindow::MNU_WEBSITE: - OpenWebsite("http://solvespace.com/helpmenu"); + case Command::WEBSITE: + Platform::OpenInBrowser("http://solvespace.com/helpmenu"); break; - case GraphicsWindow::MNU_ABOUT: - Message( -"This is SolveSpace version " PACKAGE_VERSION ".\n" + case Command::ABOUT: + Message(_( +"This is SolveSpace version %s.\n" "\n" "For more information, see http://solvespace.com/\n" "\n" @@ -816,23 +964,39 @@ void SolveSpaceUI::MenuHelp(int id) { "There is NO WARRANTY, to the extent permitted by\n" "law. For details, visit http://gnu.org/licenses/\n" "\n" -"© 2008-2016 Jonathan Westhues and other authors.\n" -); +"© 2008-%d Jonathan Westhues and other authors.\n"), +PACKAGE_VERSION, 2020); break; - default: oops(); + default: ssassert(false, "Unexpected menu ID"); } } -void SolveSpaceUI::Clear(void) { +void SolveSpaceUI::Clear() { sys.Clear(); for(int i = 0; i < MAX_UNDO; i++) { if(i < undo.cnt) undo.d[i].Clear(); if(i < redo.cnt) redo.d[i].Clear(); } + TW.window = NULL; + GW.openRecentMenu = NULL; + GW.linkRecentMenu = NULL; + GW.showGridMenuItem = NULL; + GW.perspectiveProjMenuItem = NULL; + GW.showToolbarMenuItem = NULL; + GW.showTextWndMenuItem = NULL; + GW.fullScreenMenuItem = NULL; + GW.unitsMmMenuItem = NULL; + GW.unitsMetersMenuItem = NULL; + GW.unitsInchesMenuItem = NULL; + GW.inWorkplaneMenuItem = NULL; + GW.in3dMenuItem = NULL; + GW.undoMenuItem = NULL; + GW.redoMenuItem = NULL; + GW.window = NULL; } -void Sketch::Clear(void) { +void Sketch::Clear() { group.Clear(); groupOrder.Clear(); constraint.Clear(); @@ -845,34 +1009,57 @@ void Sketch::Clear(void) { BBox Sketch::CalculateEntityBBox(bool includingInvisible) { BBox box = {}; bool first = true; - for(int i = 0; i < entity.n; i++) { - Entity *e = (Entity *)&entity.elem[i]; - if(!(e->IsVisible() || includingInvisible)) continue; - - Vector point; - double r = 0.0; - if(e->IsPoint()) { - point = e->PointGetNum(); - } else { - switch(e->type) { - case Entity::ARC_OF_CIRCLE: - case Entity::CIRCLE: - r = e->CircleGetRadiusNum(); - point = GetEntity(e->point[0])->PointGetNum(); - break; - default: continue; - } - } + auto includePoint = [&](const Vector &point) { if(first) { box.minp = point; box.maxp = point; - box.Include(point, r); first = false; } else { - box.Include(point, r); + box.Include(point); + } + }; + + for(const Entity &e : entity) { + if(e.construction) continue; + if(!(includingInvisible || e.IsVisible())) continue; + + // arc center point shouldn't be included in bounding box calculation + if(e.IsPoint() && e.h.isFromRequest()) { + Request *r = SK.GetRequest(e.h.request()); + if(r->type == Request::Type::ARC_OF_CIRCLE && e.h == r->h.entity(1)) { + continue; + } + } + + if(e.IsPoint()) { + includePoint(e.PointGetNum()); + continue; + } + + switch(e.type) { + // Circles and arcs are special cases. We calculate their bounds + // based on Bezier curve bounds. This is not exact for arcs, + // but the implementation is rather simple. + case Entity::Type::CIRCLE: + case Entity::Type::ARC_OF_CIRCLE: { + SBezierList sbl = {}; + e.GenerateBezierCurves(&sbl); + + for(const SBezier &sb : sbl.l) { + for(int j = 0; j <= sb.deg; j++) { + includePoint(sb.ctrl[j]); + } + } + sbl.Clear(); + continue; + } + + default: + continue; } } + return box; } diff --git a/src/solvespace.h b/src/solvespace.h index 6cb59c7..e8eeddc 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -4,38 +4,31 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#ifndef __SOLVESPACE_H -#define __SOLVESPACE_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#ifndef SOLVESPACE_H +#define SOLVESPACE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include +#include +#include #include -#include -#include #include +#include #include #include -#ifdef WIN32 -# include // required by GL headers -#endif -#ifdef __APPLE__ -# include // for strcasecmp in file.cpp -# include -# include -#else -# include -# include -#endif +#include +#include +#include +#include // We declare these in advance instead of simply using FT_Library // (defined as typedef FT_LibraryRec_* FT_Library) because including @@ -44,6 +37,9 @@ struct FT_LibraryRec_; struct FT_FaceRec_; +typedef struct _cairo cairo_t; +typedef struct _cairo_surface cairo_surface_t; + // The few floating-point equality comparisons in SolveSpace have been // carefully considered, so we disable the -Wfloat-equal warning for them #ifdef __clang__ @@ -57,29 +53,49 @@ struct FT_FaceRec_; #endif // Debugging functions -#ifdef NDEBUG -#define oops() do { dbp("oops at line %d, file %s\n", __LINE__, __FILE__); \ - exit(-1); } while(0) +#if defined(__GNUC__) +#define ssassert(condition, message) \ + do { \ + if(__builtin_expect((condition), true) == false) { \ + SolveSpace::AssertFailure(__FILE__, __LINE__, __func__, #condition, message); \ + __builtin_unreachable(); \ + } \ + } while(0) #else -#define oops() do { dbp("oops at line %d, file %s\n", __LINE__, __FILE__); \ - abort(); } while(0) +#define ssassert(condition, message) \ + do { \ + if((condition) == false) { \ + SolveSpace::AssertFailure(__FILE__, __LINE__, __func__, #condition, message); \ + abort(); \ + } \ + } while(0) #endif -#ifndef isnan -# define isnan(x) (((x) != (x)) || (x > 1e11) || (x < -1e11)) -#endif +#define dbp SolveSpace::Platform::DebugPrint +#define DBPTRI(tri) \ + dbp("tri: (%.3f %.3f %.3f) (%.3f %.3f %.3f) (%.3f %.3f %.3f)", \ + CO((tri).a), CO((tri).b), CO((tri).c)) namespace SolveSpace { using std::min; using std::max; using std::swap; +using std::fabs; + +[[noreturn]] +void AssertFailure(const char *file, unsigned line, const char *function, + const char *condition, const char *message); #if defined(__GNUC__) __attribute__((__format__ (__printf__, 1, 2))) #endif std::string ssprintf(const char *fmt, ...); +inline bool IsReasonable(double x) { + return std::isnan(x) || x > 1e11 || x < -1e11; +} + inline int WRAP(int v, int n) { // Clamp it to the range [0, n) while(v >= n) v -= n; @@ -99,192 +115,41 @@ inline double WRAP_SYMMETRIC(double v, double n) { return v; } -// Why is this faster than the library function? -inline double ffabs(double v) { return (v > 0) ? v : (-v); } - #define CO(v) (v).x, (v).y, (v).z -#define ANGLE_COS_EPS (1e-6) -#define LENGTH_EPS (1e-6) -#define VERY_POSITIVE (1e10) -#define VERY_NEGATIVE (-1e10) +static constexpr double ANGLE_COS_EPS = 1e-6; +static constexpr double LENGTH_EPS = 1e-6; +static constexpr double VERY_POSITIVE = 1e10; +static constexpr double VERY_NEGATIVE = -1e10; -#define isforname(c) (isalnum(c) || (c) == '_' || (c) == '-' || (c) == '#') +#include "platform/platform.h" +#include "platform/gui.h" +#include "resource.h" -#if defined(WIN32) -std::string Narrow(const wchar_t *s); -std::wstring Widen(const char *s); -std::string Narrow(const std::wstring &s); -std::wstring Widen(const std::string &s); -#endif - -inline double Random(double vmax) { - return (vmax*rand()) / RAND_MAX; -} +using Platform::AllocTemporary; +using Platform::FreeAllTemporary; class Expr; class ExprVector; class ExprQuaternion; class RgbaColor; +enum class Command : uint32_t; -//================ -// From the platform-specific code. -#if defined(WIN32) -#define PATH_SEP "\\" -#else -#define PATH_SEP "/" -#endif - -FILE *ssfopen(const std::string &filename, const char *mode); -std::fstream ssfstream(const std::string &filename, std::ios_base::openmode mode); -void ssremove(const std::string &filename); - -#define MAX_RECENT 8 -#define RECENT_OPEN (0xf000) -#define RECENT_LINK (0xf100) -extern std::string RecentFile[MAX_RECENT]; -void RefreshRecentMenus(void); - -enum DialogChoice { DIALOG_YES = 1, DIALOG_NO = -1, DIALOG_CANCEL = 0 }; -DialogChoice SaveFileYesNoCancel(void); -DialogChoice LoadAutosaveYesNo(void); -DialogChoice LocateImportedFileYesNoCancel(const std::string &filename, - bool canCancel); - -#define AUTOSAVE_SUFFIX "~" - -struct FileFilter { - const char *name; - const char *patterns[3]; +enum class Unit : uint32_t { + MM = 0, + INCHES, + METERS }; -// SolveSpace native file format -const FileFilter SlvsFileFilter[] = { - { "SolveSpace models", { "slvs" } }, - { NULL, {} } -}; -// PNG format bitmap -const FileFilter PngFileFilter[] = { - { "PNG", { "png" } }, - { NULL, {} } -}; -// Triangle mesh -const FileFilter MeshFileFilter[] = { - { "STL mesh", { "stl" } }, - { "Wavefront OBJ mesh", { "obj" } }, - { "Three.js-compatible mesh, with viewer", { "html" } }, - { "Three.js-compatible mesh, mesh only", { "js" } }, - { NULL, {} } -}; -// NURBS surfaces -const FileFilter SurfaceFileFilter[] = { - { "STEP file", { "step", "stp" } }, - { NULL, {} } -}; -// 2d vector (lines and curves) format -const FileFilter VectorFileFilter[] = { - { "PDF file", { "pdf" } }, - { "Encapsulated PostScript", { "eps", "ps" } }, - { "Scalable Vector Graphics", { "svg" } }, - { "STEP file", { "step", "stp" } }, - { "DXF file (AutoCAD 2007)", { "dxf" } }, - { "HPGL file", { "plt", "hpgl" } }, - { "G Code", { "ngc", "txt" } }, - { NULL, {} } -}; -// 3d vector (wireframe lines and curves) format -const FileFilter Vector3dFileFilter[] = { - { "STEP file", { "step", "stp" } }, - { "DXF file (AutoCAD 2007)", { "dxf" } }, - { NULL, {} } -}; -// All Importable formats -const FileFilter ImportableFileFilter[] = { - { "AutoCAD DXF and DWG files", { "dxf", "dwg" } }, - { NULL, {} } -}; -// Comma-separated value, like a spreadsheet would use -const FileFilter CsvFileFilter[] = { - { "CSV", { "csv" } }, - { NULL, {} } -}; - -bool GetSaveFile(std::string *filename, const std::string &defExtension, - const FileFilter filters[]); -bool GetOpenFile(std::string *filename, const std::string &defExtension, - const FileFilter filters[]); -std::vector GetFontFiles(); - -void OpenWebsite(const char *url); - -void CheckMenuById(int id, bool checked); -void RadioMenuById(int id, bool selected); -void EnableMenuById(int id, bool enabled); - -void ShowGraphicsEditControl(int x, int y, int fontHeight, int minWidthChars, - const std::string &str); -void HideGraphicsEditControl(void); -bool GraphicsEditControlIsVisible(void); -void ShowTextEditControl(int x, int y, const std::string &str); -void HideTextEditControl(void); -bool TextEditControlIsVisible(void); -void MoveTextScrollbarTo(int pos, int maxPos, int page); - -#define CONTEXT_SUBMENU (-1) -#define CONTEXT_SEPARATOR (-2) -void AddContextMenuItem(const char *legend, int id); -void CreateContextSubmenu(void); -int ShowContextMenu(void); - -void ToggleMenuBar(void); -bool MenuBarIsVisible(void); -void ShowTextWindow(bool visible); -void InvalidateText(void); -void InvalidateGraphics(void); -void PaintGraphics(void); -void ToggleFullScreen(void); -bool FullScreenIsActive(void); -void GetGraphicsWindowSize(int *w, int *h); -void GetTextWindowSize(int *w, int *h); -int64_t GetMilliseconds(void); -int64_t GetUnixTime(void); - -void dbp(const char *str, ...); -#define DBPTRI(tri) \ - dbp("tri: (%.3f %.3f %.3f) (%.3f %.3f %.3f) (%.3f %.3f %.3f)", \ - CO((tri).a), CO((tri).b), CO((tri).c)) - -void SetCurrentFilename(const std::string &filename); -void SetMousePointerToHand(bool yes); -void DoMessageBox(const char *str, int rows, int cols, bool error); -void SetTimerFor(int milliseconds); -void SetAutosaveTimerFor(int minutes); -void ScheduleLater(); -void ExitNow(void); - -void CnfFreezeInt(uint32_t val, const std::string &name); -void CnfFreezeFloat(float val, const std::string &name); -void CnfFreezeString(const std::string &val, const std::string &name); -std::string CnfThawString(const std::string &val, const std::string &name); -uint32_t CnfThawInt(uint32_t val, const std::string &name); -float CnfThawFloat(float val, const std::string &name); - -void *AllocTemporary(size_t n); -void FreeTemporary(void *p); -void FreeAllTemporary(void); -void *MemAlloc(size_t n); -void MemFree(void *p); -void InitHeaps(void); -void vl(void); // debug function to validate heaps - -// End of platform-specific functions -//================ +template +using handle_map = std::map; class Group; class SSurface; #include "dsc.h" #include "polygon.h" #include "srf/surface.h" +#include "render/render.h" class Entity; class hEntity; @@ -293,6 +158,15 @@ class hParam; typedef IdList EntityList; typedef IdList ParamList; +enum class SolveResult : uint32_t { + OKAY = 0, + DIDNT_CONVERGE = 10, + REDUNDANT_OKAY = 11, + REDUNDANT_DIDNT_CONVERGE = 12, + TOO_MANY_UNKNOWNS = 20 +}; + + #include "sketch.h" #include "ui.h" #include "expr.h" @@ -309,64 +183,16 @@ public: utf8_iterator& operator++() { **this; p=n; n=NULL; return *this; } utf8_iterator operator++(int) { utf8_iterator t(*this); operator++(); return t; } char32_t operator*(); + const char* ptr() const { return p; } }; class ReadUTF8 { const std::string &str; public: ReadUTF8(const std::string &str) : str(str) {} utf8_iterator begin() const { return utf8_iterator(&str[0]); } - utf8_iterator end() const { return utf8_iterator(&str[str.length()]); } + utf8_iterator end() const { return utf8_iterator(&str[0] + str.length()); } }; -void ssglLineWidth(GLfloat width); -void ssglVertex3v(Vector u); -void ssglAxisAlignedQuad(double l, double r, double t, double b, bool lone = true); -void ssglAxisAlignedLineLoop(double l, double r, double t, double b); -#ifdef WIN32 -# define SSGL_CALLBACK __stdcall -#else -# define SSGL_CALLBACK -#endif -extern "C" { typedef void SSGL_CALLBACK ssglCallbackFptr(void); } -void ssglTesselatePolygon(GLUtesselator *gt, SPolygon *p); -void ssglFillPolygon(SPolygon *p); -void ssglFillMesh(bool useSpecColor, RgbaColor color, - SMesh *m, uint32_t h, uint32_t s1, uint32_t s2); -void ssglDebugPolygon(SPolygon *p); -void ssglDrawEdges(SEdgeList *l, bool endpointsToo, hStyle hs); -void ssglDrawOutlines(SOutlineList *l, Vector projDir, hStyle hs); -void ssglDebugMesh(SMesh *m); -void ssglMarkPolygonNormal(SPolygon *p); -typedef void ssglLineFn(void *data, Vector a, Vector b); -void ssglWriteText(const std::string &str, double h, Vector t, Vector u, Vector v, - ssglLineFn *fn, void *fndata); -void ssglWriteTextRefCenter(const std::string &str, double h, Vector t, Vector u, Vector v, - ssglLineFn *fn, void *fndata); -double ssglStrCapHeight(double h); -double ssglStrFontSize(double h); -double ssglStrWidth(const std::string &str, double h); -void ssglLockColorTo(RgbaColor rgb); -void ssglStippledLine(Vector a, Vector b, double width, - int stippleType, double stippleScale, bool maybeFat); -void ssglStippledLine(Vector a, Vector b, double width, - const char *stipplePattern, double stippleScale, bool maybeFat); -void ssglFatLine(Vector a, Vector b, double width); -void ssglUnlockColor(void); -void ssglColorRGB(RgbaColor rgb); -void ssglColorRGBa(RgbaColor rgb, double a); -void ssglDepthRangeOffset(int units); -void ssglDepthRangeLockToFront(bool yes); -void ssglDrawPixelsWithTexture(uint8_t *data, int w, int h); -void ssglInitializeBitmapFont(); -void ssglBitmapText(const std::string &str, Vector p); -void ssglBitmapCharQuad(char32_t chr, double x, double y); -int ssglBitmapCharWidth(char32_t chr); -#define TEXTURE_BACKGROUND_IMG 10 -#define TEXTURE_DRAW_PIXELS 20 -#define TEXTURE_COLOR_PICKER_2D 30 -#define TEXTURE_COLOR_PICKER_1D 40 -#define TEXTURE_BITMAP_FONT 50 - #define arraylen(x) (sizeof((x))/sizeof((x)[0])) #define PI (3.1415926535897931) @@ -374,16 +200,12 @@ void MakeMatrix(double *mat, double a11, double a12, double a13, double a14, double a21, double a22, double a23, double a24, double a31, double a32, double a33, double a34, double a41, double a42, double a43, double a44); -std::string MakeAcceleratorLabel(int accel); -bool FilenameHasExtension(const std::string &str, const char *ext); -bool ReadFile(const std::string &filename, std::string *data); -bool WriteFile(const std::string &filename, const std::string &data); -void Message(const char *str, ...); -void Error(const char *str, ...); -void CnfFreezeBool(bool v, const std::string &name); -void CnfFreezeColor(RgbaColor v, const std::string &name); -bool CnfThawBool(bool v, const std::string &name); -RgbaColor CnfThawColor(RgbaColor v, const std::string &name); +void MultMatrix(double *mata, double *matb, double *matr); + +int64_t GetMilliseconds(); +void Message(const char *fmt, ...); +void MessageAndRun(std::function onDismiss, const char *fmt, ...); +void Error(const char *fmt, ...); class System { public: @@ -436,48 +258,51 @@ public: } mat; static const double RANK_MAG_TOLERANCE, CONVERGE_TOLERANCE; - int CalculateRank(void); - bool TestRank(void); + int CalculateRank(); + bool TestRank(int *rank = NULL); static bool SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS], double B[], int N); - bool SolveLeastSquares(void); + bool SolveLeastSquares(); bool WriteJacobian(int tag); - void EvalJacobian(void); + void EvalJacobian(); void WriteEquationsExceptFor(hConstraint hc, Group *g); - void FindWhichToRemoveToFixJacobian(Group *g, List *bad); - void SolveBySubstitution(void); + void FindWhichToRemoveToFixJacobian(Group *g, List *bad, + bool forceDofCheck); + void SolveBySubstitution(); bool IsDragged(hParam p); bool NewtonSolve(int tag); - enum { - SOLVED_OKAY = 0, - DIDNT_CONVERGE = 10, - REDUNDANT_OKAY = 11, - REDUNDANT_DIDNT_CONVERGE = 12, - TOO_MANY_UNKNOWNS = 20 - }; - int Solve(Group *g, int *dof, List *bad, - bool andFindBad, bool andFindFree); + void MarkParamsFree(bool findFree); + int CalculateDof(); + + SolveResult Solve(Group *g, int *rank = NULL, int *dof = NULL, + List *bad = NULL, + bool andFindBad = false, bool andFindFree = false, + bool forceDofCheck = false); - void Clear(void); + SolveResult SolveRank(Group *g, int *rank = NULL, int *dof = NULL, + List *bad = NULL, + bool andFindBad = false, bool andFindFree = false); + + void Clear(); }; #include "ttf.h" class StepFileWriter { public: - void ExportSurfacesTo(const std::string &filename); - void WriteHeader(void); - void WriteProductHeader(void); + void ExportSurfacesTo(const Platform::Path &filename); + void WriteHeader(); + void WriteProductHeader(); int ExportCurve(SBezier *sb); int ExportCurveLoop(SBezierLoop *loop, bool inner); void ExportSurface(SSurface *ss, SBezierList *sbl); - void WriteWireframe(void); - void WriteFooter(void); + void WriteWireframe(); + void WriteFooter(); List curves; List advancedFaces; @@ -492,14 +317,12 @@ protected: public: FILE *f; - std::string filename; + Platform::Path filename; Vector ptMin, ptMax; static double MmToPts(double mm); - static VectorFileWriter *ForFile(const std::string &filename); - - ~VectorFileWriter(); + static VectorFileWriter *ForFile(const Platform::Path &filename); void SetModelviewProjection(const Vector &u, const Vector &v, const Vector &n, const Vector &origin, double cameraTan, double scale); @@ -510,18 +333,18 @@ public: void BezierAsPwl(SBezier *sb); void BezierAsNonrationalCubic(SBezier *sb, int depth=0); - virtual bool OutputConstraints(IdList *) { return false; } - virtual bool CanOutputMesh() const { return false; } - - virtual void StartPath( RgbaColor strokeRgb, double lineWidth, + virtual void StartPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) = 0; virtual void FinishPath(RgbaColor strokeRgb, double lineWidth, bool filled, RgbaColor fillRgb, hStyle hs) = 0; virtual void Bezier(SBezier *sb) = 0; virtual void Triangle(STriangle *tr) = 0; - virtual void StartFile(void) = 0; - virtual void FinishAndCloseFile(void) = 0; - virtual bool HasCanvasSize(void) = 0; + virtual bool OutputConstraints(IdList *) { return false; } + virtual void Background(RgbaColor color) = 0; + virtual void StartFile() = 0; + virtual void FinishAndCloseFile() = 0; + virtual bool HasCanvasSize() const = 0; + virtual bool CanOutputMesh() const = 0; }; class DxfFileWriter : public VectorFileWriter { public: @@ -532,21 +355,22 @@ public: std::vector paths; IdList *constraint; - bool OutputConstraints(IdList *constraint); + static const char *lineTypeName(StipplePattern stippleType); + + bool OutputConstraints(IdList *constraint) override; void StartPath( RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); + bool filled, RgbaColor fillRgb, hStyle hs) override; void FinishPath(RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); - void Triangle(STriangle *tr); - void Bezier(SBezier *sb); - void StartFile(void); - void FinishAndCloseFile(void); - bool HasCanvasSize(void) { return false; } + bool filled, RgbaColor fillRgb, hStyle hs) override; + void Triangle(STriangle *tr) override; + void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; + void StartFile() override; + void FinishAndCloseFile() override; + bool HasCanvasSize() const override { return false; } + bool CanOutputMesh() const override { return false; } bool NeedToOutput(Constraint *c); - - static const char *lineTypeName(int stippleType); - }; class EpsFileWriter : public VectorFileWriter { public: @@ -554,15 +378,16 @@ public: void MaybeMoveTo(Vector s, Vector f); void StartPath( RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); + bool filled, RgbaColor fillRgb, hStyle hs) override; void FinishPath(RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); - void Triangle(STriangle *tr); - void Bezier(SBezier *sb); - void StartFile(void); - void FinishAndCloseFile(void); - bool HasCanvasSize(void) { return true; } - bool CanOutputMesh() const { return true; } + bool filled, RgbaColor fillRgb, hStyle hs) override; + void Triangle(STriangle *tr) override; + void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; + void StartFile() override; + void FinishAndCloseFile() override; + bool HasCanvasSize() const override { return true; } + bool CanOutputMesh() const override { return true; } }; class PdfFileWriter : public VectorFileWriter { public: @@ -572,15 +397,16 @@ public: void MaybeMoveTo(Vector s, Vector f); void StartPath( RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); + bool filled, RgbaColor fillRgb, hStyle hs) override; void FinishPath(RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); - void Triangle(STriangle *tr); - void Bezier(SBezier *sb); - void StartFile(void); - void FinishAndCloseFile(void); - bool HasCanvasSize(void) { return true; } - bool CanOutputMesh() const { return true; } + bool filled, RgbaColor fillRgb, hStyle hs) override; + void Triangle(STriangle *tr) override; + void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; + void StartFile() override; + void FinishAndCloseFile() override; + bool HasCanvasSize() const override { return true; } + bool CanOutputMesh() const override { return true; } }; class SvgFileWriter : public VectorFileWriter { public: @@ -588,53 +414,60 @@ public: void MaybeMoveTo(Vector s, Vector f); void StartPath( RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); + bool filled, RgbaColor fillRgb, hStyle hs) override; void FinishPath(RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); - void Triangle(STriangle *tr); - void Bezier(SBezier *sb); - void StartFile(void); - void FinishAndCloseFile(void); - bool HasCanvasSize(void) { return true; } - bool CanOutputMesh() const { return true; } + bool filled, RgbaColor fillRgb, hStyle hs) override; + void Triangle(STriangle *tr) override; + void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; + void StartFile() override; + void FinishAndCloseFile() override; + bool HasCanvasSize() const override { return true; } + bool CanOutputMesh() const override { return true; } }; class HpglFileWriter : public VectorFileWriter { public: static double MmToHpglUnits(double mm); void StartPath( RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); + bool filled, RgbaColor fillRgb, hStyle hs) override; void FinishPath(RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); - void Triangle(STriangle *tr); - void Bezier(SBezier *sb); - void StartFile(void); - void FinishAndCloseFile(void); - bool HasCanvasSize(void) { return false; } + bool filled, RgbaColor fillRgb, hStyle hs) override; + void Triangle(STriangle *tr) override; + void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; + void StartFile() override; + void FinishAndCloseFile() override; + bool HasCanvasSize() const override { return false; } + bool CanOutputMesh() const override { return false; } }; class Step2dFileWriter : public VectorFileWriter { StepFileWriter sfw; void StartPath( RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); + bool filled, RgbaColor fillRgb, hStyle hs) override; void FinishPath(RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); - void Triangle(STriangle *tr); - void Bezier(SBezier *sb); - void StartFile(void); - void FinishAndCloseFile(void); - bool HasCanvasSize(void) { return false; } + bool filled, RgbaColor fillRgb, hStyle hs) override; + void Triangle(STriangle *tr) override; + void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; + void StartFile() override; + void FinishAndCloseFile() override; + bool HasCanvasSize() const override { return false; } + bool CanOutputMesh() const override { return false; } }; class GCodeFileWriter : public VectorFileWriter { public: SEdgeList sel; void StartPath( RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); + bool filled, RgbaColor fillRgb, hStyle hs) override; void FinishPath(RgbaColor strokeRgb, double lineWidth, - bool filled, RgbaColor fillRgb, hStyle hs); - void Triangle(STriangle *tr); - void Bezier(SBezier *sb); - void StartFile(void); - void FinishAndCloseFile(void); - bool HasCanvasSize(void) { return false; } + bool filled, RgbaColor fillRgb, hStyle hs) override; + void Triangle(STriangle *tr) override; + void Bezier(SBezier *sb) override; + void Background(RgbaColor color) override; + void StartFile() override; + void FinishAndCloseFile() override; + bool HasCanvasSize() const override { return false; } + bool CanOutputMesh() const override { return false; } }; #ifdef LIBRARY @@ -665,7 +498,7 @@ public: inline Group *GetGroup (hGroup h) { return group. FindById(h); } // Styles are handled a bit differently. - void Clear(void); + void Clear(); BBox CalculateEntityBBox(bool includingInvisible); Group *GetRunningMeshGroupFor(hGroup h); @@ -680,7 +513,7 @@ public: GraphicsWindow GW; // The state for undo/redo - typedef struct { + typedef struct UndoState { IdList group; List
groupOrder; IdList request; @@ -689,7 +522,7 @@ public: IdList style; hGroup activeGroup; - void Clear(void) { + void Clear() { group.Clear(); request.Clear(); constraint.Clear(); @@ -697,7 +530,7 @@ public: style.Clear(); } } UndoState; - enum { MAX_UNDO = 16 }; + enum { MAX_UNDO = 100 }; typedef struct { UndoState d[MAX_UNDO]; int cnt; @@ -705,10 +538,15 @@ public: } UndoStack; UndoStack undo; UndoStack redo; - void UndoEnableMenus(void); - void UndoRemember(void); - void UndoUndo(void); - void UndoRedo(void); + + std::map, Platform::PathLess> images; + bool ReloadLinkedImage(const Platform::Path &saveFile, Platform::Path *filename, + bool canCancel); + + void UndoEnableMenus(); + void UndoRemember(); + void UndoUndo(); + void UndoRedo(); void PushFromCurrentOnto(UndoStack *uk); void PopOntoCurrentFrom(UndoStack *uk); void UndoClearState(UndoState *ut); @@ -725,78 +563,83 @@ public: int maxSegments; double exportChordTol; int exportMaxSegments; + int timeoutRedundantConstr; //milliseconds double cameraTangent; - float gridSpacing; - float exportScale; - float exportOffset; + double gridSpacing; + double exportScale; + double exportOffset; bool fixExportColors; + bool exportBackgroundColor; bool drawBackFaces; + bool showContourAreas; bool checkClosedContour; + bool turntableNav; + bool immediatelyEditDimension; + bool automaticLineConstraints; bool showToolbar; + Platform::Path screenshotFile; RgbaColor backgroundColor; bool exportShadedTriangles; bool exportPwlCurves; bool exportCanvasSizeAuto; bool exportMode; struct { - float left; - float right; - float bottom; - float top; + double left; + double right; + double bottom; + double top; } exportMargin; struct { - float width; - float height; - float dx; - float dy; + double width; + double height; + double dx; + double dy; } exportCanvas; struct { - float depth; + double depth; int passes; - float feed; - float plungeFeed; + double feed; + double plungeFeed; } gCode; - typedef enum { - UNIT_MM = 0, - UNIT_INCHES - } Unit; Unit viewUnits; int afterDecimalMm; int afterDecimalInch; + int afterDecimalDegree; + bool useSIPrefixes; int autosaveInterval; // in minutes std::string MmToString(double v); + std::string MmToStringSI(double v, int dim = 0); + std::string DegreeToString(double v); double ExprToMm(Expr *e); double StringToMm(const std::string &s); - const char *UnitName(void); - double MmPerUnit(void); - int UnitDigitsAfterDecimal(void); + const char *UnitName(); + double MmPerUnit(); + int UnitDigitsAfterDecimal(); void SetUnitDigitsAfterDecimal(int v); - double ChordTolMm(void); - double ExportChordTolMm(void); - int GetMaxSegments(void); + double ChordTolMm(); + double ExportChordTolMm(); + int GetMaxSegments(); bool usePerspectiveProj; - double CameraTangent(void); + double CameraTangent(); // Some stuff relating to the tangent arcs created non-parametrically // as special requests. double tangentArcRadius; bool tangentArcManual; - bool tangentArcDeleteOld; + bool tangentArcModify; // The platform-dependent code calls this before entering the msg loop - void Init(void); - bool OpenFile(const std::string &filename); - void Exit(void); + void Init(); + void Exit(); // File load/save routines, including the additional files that get // loaded when we have link groups. FILE *fh; - void AfterNewFile(void); - static void RemoveFromRecentList(const std::string &filename); - static void AddToRecentList(const std::string &filename); - std::string saveFile; + void AfterNewFile(); + void AddToRecentList(const Platform::Path &filename); + Platform::Path saveFile; bool fileLoadError; bool unsaved; typedef struct { @@ -806,8 +649,8 @@ public: void *ptr; } SaveTable; static const SaveTable SAVED[]; - void SaveUsingTable(int type); - void LoadUsingTable(char *key, char *val); + void SaveUsingTable(const Platform::Path &filename, int type); + void LoadUsingTable(const Platform::Path &filename, char *key, char *val); struct { Group g; Request r; @@ -816,38 +659,49 @@ public: Constraint c; Style s; } sv; - static void MenuFile(int id); - bool Autosave(); + static void MenuFile(Command id); + void Autosave(); void RemoveAutosave(); + static constexpr size_t MAX_RECENT = 8; + static constexpr const char *SKETCH_EXT = "slvs"; + static constexpr const char *BACKUP_EXT = "slvs~"; + std::vector recentFiles; + bool Load(const Platform::Path &filename); bool GetFilenameAndSave(bool saveAs); - bool OkayToStartNewFile(void); - hGroup CreateDefaultDrawingGroup(void); - void UpdateWindowTitle(void); - void ClearExisting(void); - void NewFile(void); - bool SaveToFile(const std::string &filename); - bool LoadAutosaveFor(const std::string &filename); - bool LoadFromFile(const std::string &filename); - bool LoadEntitiesFromFile(const std::string &filename, EntityList *le, + bool OkayToStartNewFile(); + hGroup CreateDefaultDrawingGroup(); + void UpdateWindowTitles(); + void ClearExisting(); + void NewFile(); + bool SaveToFile(const Platform::Path &filename); + bool LoadAutosaveFor(const Platform::Path &filename); + bool LoadFromFile(const Platform::Path &filename, bool canCancel = false); + void UpgradeLegacyData(); + bool LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh); - bool ReloadAllImported(bool canCancel=false); + bool LoadEntitiesFromSlvs(const Platform::Path &filename, EntityList *le, + SMesh *m, SShell *sh); + bool ReloadAllLinked(const Platform::Path &filename, bool canCancel = false); // And the various export options - void ExportAsPngTo(const std::string &filename); - void ExportMeshTo(const std::string &filename); + void ExportAsPngTo(const Platform::Path &filename); + void ExportMeshTo(const Platform::Path &filename); void ExportMeshAsStlTo(FILE *f, SMesh *sm); - void ExportMeshAsObjTo(FILE *f, SMesh *sm); - void ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, - SMesh *sm, SEdgeList *sel); - void ExportViewOrWireframeTo(const std::string &filename, bool wireframe); - void ExportSectionTo(const std::string &filename); + void ExportMeshAsQ3doTo(FILE *f, SMesh *sm); + void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm); + void ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename, + SMesh *sm, SOutlineList *sol); + void ExportMeshAsVrmlTo(FILE *f, const Platform::Path &filename, SMesh *sm); + void ExportViewOrWireframeTo(const Platform::Path &filename, bool exportWireframe); + void ExportSectionTo(const Platform::Path &filename); void ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl, VectorFileWriter *out); void ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm, - Vector u, Vector v, Vector n, Vector origin, - double cameraTan, + Vector u, Vector v, + Vector n, Vector origin, + double cameraTan, VectorFileWriter *out); - static void MenuAnalyze(int id); + static void MenuAnalyze(Command id); // Additional display stuff struct { @@ -860,30 +714,28 @@ public: Vector ptA; Vector ptB; } extraLine; - struct { - uint8_t *fromFile; - int w, h; - int rw, rh; - double scale; // pixels per mm - Vector origin; - } bgImage; struct { bool draw, showOrigin; Vector pt, u, v; } justExportedInfo; + struct { + bool draw; + bool dirty; + Vector position; + } centerOfMass; class Clipboard { public: List r; List c; - void Clear(void); + void Clear(); bool ContainsEntity(hEntity old); hEntity NewEntityFor(hEntity old); }; Clipboard clipboard; - void MarkGroupDirty(hGroup hg); + void MarkGroupDirty(hGroup hg, bool onlyThis = false); void MarkGroupDirtyByEntity(hEntity he); // Consistency checking on the sketch: stuff with missing dependencies @@ -895,28 +747,32 @@ public: int nonTrivialConstraints; } deleted; bool GroupExists(hGroup hg); - bool PruneOrphans(void); + bool PruneOrphans(); bool EntityExists(hEntity he); bool GroupsInOrder(hGroup before, hGroup after); bool PruneGroups(hGroup hg); bool PruneRequests(hGroup hg); bool PruneConstraints(hGroup hg); + static void ShowNakedEdges(bool reportOnlyWhenNotOkay); - enum GenerateType { - GENERATE_DIRTY, - GENERATE_ALL, - GENERATE_REGEN, - GENERATE_UNTIL_ACTIVE, + enum class Generate : uint32_t { + DIRTY, + ALL, + REGEN, + UNTIL_ACTIVE, }; - void GenerateAll(GenerateType type = GENERATE_DIRTY, bool andFindFree = false, + void GenerateAll(Generate type = Generate::DIRTY, bool andFindFree = false, bool genForBBox = false); void SolveGroup(hGroup hg, bool andFindFree); void SolveGroupAndReport(hGroup hg, bool andFindFree); - void MarkDraggedParams(void); - void ForceReferences(void); + SolveResult TestRankForGroup(hGroup hg, int *rank = NULL); + void WriteEqSystemForGroup(hGroup hg); + void MarkDraggedParams(); + void ForceReferences(); + void UpdateCenterOfMass(); - bool ActiveGroupsOkay(void); + bool ActiveGroupsOkay(); // The system to be solved. System *pSys; @@ -930,25 +786,23 @@ public: // the sketch! bool allConsistent; - struct { - bool scheduled; - bool showTW; - bool generateAll; - } later; + Platform::TimerRef showTWTimer; + Platform::TimerRef generateAllTimer; + Platform::TimerRef autosaveTimer; void ScheduleShowTW(); void ScheduleGenerateAll(); - void DoLater(void); + void ScheduleAutosave(); - static void MenuHelp(int id); + static void MenuHelp(Command id); - void Clear(void); + void Clear(); // We allocate TW and sys on the heap to work around an MSVC problem // where it puts zero-initialized global data in the binary (~30M of zeroes) // in release builds. SolveSpaceUI() - : pTW(new TextWindow({})), TW(*pTW), - pSys(new System({})), sys(*pSys) {} + : pTW(new TextWindow()), TW(*pTW), + pSys(new System()), sys(*pSys) {} ~SolveSpaceUI() { delete pTW; @@ -956,13 +810,14 @@ public: } }; -void ImportDxf(const std::string &file); -void ImportDwg(const std::string &file); +void ImportDxf(const Platform::Path &file); +void ImportDwg(const Platform::Path &file); +bool LinkIDF(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh); extern SolveSpaceUI SS; extern Sketch SK; -}; +} #ifndef __OBJC__ using namespace SolveSpace; diff --git a/src/srf/boolean.cpp b/src/srf/boolean.cpp index d064952..1edf46e 100644 --- a/src/srf/boolean.cpp +++ b/src/srf/boolean.cpp @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -// Top-level functions to compute the Boolean union or difference between -// two shells of rational polynomial surfaces. +// Top-level functions to compute the Boolean union, difference or intersection +// between two shells of rational polynomial surfaces. // // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- @@ -9,11 +9,59 @@ static int I; void SShell::MakeFromUnionOf(SShell *a, SShell *b) { - MakeFromBoolean(a, b, AS_UNION); + MakeFromBoolean(a, b, SSurface::CombineAs::UNION); } void SShell::MakeFromDifferenceOf(SShell *a, SShell *b) { - MakeFromBoolean(a, b, AS_DIFFERENCE); + MakeFromBoolean(a, b, SSurface::CombineAs::DIFFERENCE); +} + +void SShell::MakeFromIntersectionOf(SShell *a, SShell *b) { + MakeFromBoolean(a, b, SSurface::CombineAs::INTERSECTION); +} + +void SCurve::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const { + *ptMax = {VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE}; + *ptMin = {VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE}; + + for(int i = 0; i <= exact.deg; i++) { + exact.ctrl[i].MakeMaxMin(ptMax, ptMin); + } +} + +// We will be inserting other curve verticies into our curves to split them. +// This is helpful when curved surfaces become tangent along a trim and the +// usual tests for curve-surface intersection don't split the curve at a vertex. +// This is faster than the previous version that split at surface corners and +// handles more buggy cases. It's not clear this is the best way but it works ok. +static void FindVertsOnCurve(List *l, const SCurve *curve, SShell *sh) { + + Vector amax, amin; + curve->GetAxisAlignedBounding(&amax, &amin); + + for(auto sc : sh->curve) { + if(!sc.isExact) continue; + + Vector cmax, cmin; + sc.GetAxisAlignedBounding(&cmax, &cmin); + + if(Vector::BoundingBoxesDisjoint(amax, amin, cmax, cmin)) { + // They cannot possibly intersect, no curves to generate + continue; + } + + for(int i=0; i<2; i++) { + Vector pt = sc.exact.ctrl[ i==0 ? 0 : sc.exact.deg ]; + double t; + curve->exact.ClosestPointTo(pt, &t, /*must converge=*/ false); + double d = pt.Minus(curve->exact.PointAt(t)).Magnitude(); + if((t>LENGTH_EPS) && (t<(1.0-LENGTH_EPS)) && (d < LENGTH_EPS)) { + SInter inter; + inter.p = pt; + l->Add(&inter); + } + } + } } //----------------------------------------------------------------------------- @@ -24,40 +72,40 @@ void SShell::MakeFromDifferenceOf(SShell *a, SShell *b) { // the intersection of srfA and srfB.) Return a new pwl curve with everything // split. //----------------------------------------------------------------------------- -static Vector LineStart, LineDirection; -static int ByTAlongLine(const void *av, const void *bv) -{ - SInter *a = (SInter *)av, - *b = (SInter *)bv; - - double ta = (a->p.Minus(LineStart)).DivPivoting(LineDirection), - tb = (b->p.Minus(LineStart)).DivPivoting(LineDirection); - - return (ta > tb) ? 1 : -1; -} SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, - SSurface *srfA, SSurface *srfB) + SSurface *srfA, SSurface *srfB) const { SCurve ret; ret = *this; ret.pts = {}; - SCurvePt *p = pts.First(); - if(!p) oops(); + // First find any vertex that lies on our curve. + List vertpts = {}; + if(isExact) { + if(agnstA) + FindVertsOnCurve(&vertpts, this, agnstA); + if(agnstB) + FindVertsOnCurve(&vertpts, this, agnstB); + } + + const SCurvePt *p = pts.First(); + ssassert(p != NULL, "Cannot split an empty curve"); SCurvePt prev = *p; ret.pts.Add(p); p = pts.NextAfter(p); - + for(; p; p = pts.NextAfter(p)) { List il = {}; // Find all the intersections with the two passed shells if(agnstA) - agnstA->AllPointsIntersecting(prev.p, p->p, &il, true, false, true); + agnstA->AllPointsIntersecting(prev.p, p->p, &il, + /*asSegment=*/true, /*trimmed=*/false, /*inclTangent=*/true); if(agnstB) - agnstB->AllPointsIntersecting(prev.p, p->p, &il, true, false, true); + agnstB->AllPointsIntersecting(prev.p, p->p, &il, + /*asSegment=*/true, /*trimmed=*/false, /*inclTangent=*/true); - if(il.n > 0) { + if(!il.IsEmpty()) { // The intersections were generated by intersecting the pwl // edge against a surface; so they must be refined to lie // exactly on the original curve. @@ -76,15 +124,15 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, } Point2d puv; - (pi->srf)->ClosestPointTo(pi->p, &puv, false); + (pi->srf)->ClosestPointTo(pi->p, &puv, /*mustConverge=*/false); // Split the edge if the intersection lies within the surface's // trim curves, or within the chord tol of the trim curve; want // some slop if points are close to edge and pwl is too coarse, // and it doesn't hurt to split unnecessarily. Point2d dummy = { 0, 0 }; - int c = (pi->srf->bsp) ? pi->srf->bsp->ClassifyPoint(puv, dummy, pi->srf) : SBspUv::OUTSIDE; - if(c == SBspUv::OUTSIDE) { + SBspUv::Class c = (pi->srf->bsp) ? pi->srf->bsp->ClassifyPoint(puv, dummy, pi->srf) : SBspUv::Class::OUTSIDE; + if(c == SBspUv::Class::OUTSIDE) { double d = VERY_POSITIVE; if(pi->srf->bsp) d = pi->srf->bsp->MinimumDistanceToEdge(puv, pi->srf); if(d > SS.ChordTolMm()) { @@ -93,18 +141,40 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, } } - // We're keeping the intersection, so actually refine it. - (pi->srf)->PointOnSurfaces(srfA, srfB, &(puv.x), &(puv.y)); + // We're keeping the intersection, so actually refine it. Finding the intersection + // to within EPS is important to match the ends of different chopped trim curves. + // The general 3-surface intersection fails to refine for trims where surfaces + // are tangent at the curve, but those trims are usually exact, so… + if(isExact) { + (pi->srf)->PointOnCurve(&exact, &(puv.x), &(puv.y)); + } else { + (pi->srf)->PointOnSurfaces(srfA, srfB, &(puv.x), &(puv.y)); + } pi->p = (pi->srf)->PointAt(puv); } il.RemoveTagged(); + } + // Now add any vertex that is on this segment + const Vector lineStart = prev.p; + const Vector lineDirection = (p->p).Minus(prev.p); + for(auto vtx : vertpts) { + double t = (vtx.p.Minus(lineStart)).DivProjected(lineDirection); + if((0.0 < t) && (t < 1.0)) { + il.Add(&vtx); + } + } + if(!il.IsEmpty()) { + SInter *pi; // And now sort them in order along the line. Note that we must // do that after refining, in case the refining would make two // points switch places. - LineStart = prev.p; - LineDirection = (p->p).Minus(prev.p); - qsort(il.elem, il.n, sizeof(il.elem[0]), ByTAlongLine); + std::sort(il.begin(), il.end(), [&](const SInter &a, const SInter &b) { + double ta = (a.p.Minus(lineStart)).DivProjected(lineDirection); + double tb = (b.p.Minus(lineStart)).DivProjected(lineDirection); + + return (ta < tb); + }); // And now uses the intersections to generate our split pwl edge(s) Vector prev = Vector::From(VERY_POSITIVE, 0, 0); @@ -126,20 +196,24 @@ SCurve SCurve::MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, ret.pts.Add(p); prev = *p; } + vertpts.Clear(); return ret; } void SShell::CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into) { - SCurve *sc; - for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) { +#pragma omp parallel for + for(int i=0; iMakeCopySplitAgainst(agnst, NULL, surface.FindById(sc->surfA), surface.FindById(sc->surfB)); - scn.source = opA ? SCurve::FROM_A : SCurve::FROM_B; - - hSCurve hsc = into->curve.AddAndAssignId(&scn); - // And note the new ID so that we can rewrite the trims appropriately - sc->newH = hsc; + scn.source = opA ? SCurve::Source::A : SCurve::Source::B; +#pragma omp critical + { + hSCurve hsc = into->curve.AddAndAssignId(&scn); + // And note the new ID so that we can rewrite the trims appropriately + sc->newH = hsc; + } } } @@ -194,39 +268,42 @@ void SSurface::TrimFromEdgeList(SEdgeList *el, bool asUv) { } } -static bool KeepRegion(int type, bool opA, int shell, int orig) +static bool KeepRegion(SSurface::CombineAs type, bool opA, SShell::Class shell, SShell::Class orig) { - bool inShell = (shell == SShell::INSIDE), - inSame = (shell == SShell::COINC_SAME), - inOpp = (shell == SShell::COINC_OPP), - inOrig = (orig == SShell::INSIDE); + bool inShell = (shell == SShell::Class::INSIDE), + outSide = (shell == SShell::Class::OUTSIDE), + inSame = (shell == SShell::Class::COINC_SAME), + inOrig = (orig == SShell::Class::INSIDE); - bool inFace = inSame || inOpp; - - // If these are correct, then they should be independent of inShell - // if inFace is true. if(!inOrig) return false; switch(type) { - case SShell::AS_UNION: + case SSurface::CombineAs::UNION: if(opA) { - return (!inShell && !inFace); + return outSide; } else { - return (!inShell && !inFace) || inSame; + return outSide || inSame; } - case SShell::AS_DIFFERENCE: + case SSurface::CombineAs::DIFFERENCE: if(opA) { - return (!inShell && !inFace); + return outSide; } else { - return (inShell && !inFace) || inSame; + return inShell || inSame; } - default: oops(); + case SSurface::CombineAs::INTERSECTION: + if(opA) { + return inShell; + } else { + return inShell || inSame; + } + + default: ssassert(false, "Unexpected combine type"); } } -static bool KeepEdge(int type, bool opA, - int indir_shell, int outdir_shell, - int indir_orig, int outdir_orig) +static bool KeepEdge(SSurface::CombineAs type, bool opA, + SShell::Class indir_shell, SShell::Class outdir_shell, + SShell::Class indir_orig, SShell::Class outdir_orig) { bool keepIn = KeepRegion(type, opA, indir_shell, indir_orig), keepOut = KeepRegion(type, opA, outdir_shell, outdir_orig); @@ -237,33 +314,33 @@ static bool KeepEdge(int type, bool opA, return false; } -static void TagByClassifiedEdge(int bspclass, int *indir, int *outdir) +static void TagByClassifiedEdge(SBspUv::Class bspclass, SShell::Class *indir, SShell::Class *outdir) { switch(bspclass) { - case SBspUv::INSIDE: - *indir = SShell::INSIDE; - *outdir = SShell::INSIDE; + case SBspUv::Class::INSIDE: + *indir = SShell::Class::INSIDE; + *outdir = SShell::Class::INSIDE; break; - case SBspUv::OUTSIDE: - *indir = SShell::OUTSIDE; - *outdir = SShell::OUTSIDE; + case SBspUv::Class::OUTSIDE: + *indir = SShell::Class::OUTSIDE; + *outdir = SShell::Class::OUTSIDE; break; - case SBspUv::EDGE_PARALLEL: - *indir = SShell::INSIDE; - *outdir = SShell::OUTSIDE; + case SBspUv::Class::EDGE_PARALLEL: + *indir = SShell::Class::INSIDE; + *outdir = SShell::Class::OUTSIDE; break; - case SBspUv::EDGE_ANTIPARALLEL: - *indir = SShell::OUTSIDE; - *outdir = SShell::INSIDE; + case SBspUv::Class::EDGE_ANTIPARALLEL: + *indir = SShell::Class::OUTSIDE; + *outdir = SShell::Class::INSIDE; break; default: dbp("TagByClassifiedEdge: fail!"); - *indir = SShell::OUTSIDE; - *outdir = SShell::OUTSIDE; + *indir = SShell::Class::OUTSIDE; + *outdir = SShell::Class::OUTSIDE; break; } } @@ -295,18 +372,18 @@ static void DEBUGEDGELIST(SEdgeList *sel, SSurface *surf) { void SSurface::FindChainAvoiding(SEdgeList *src, SEdgeList *dest, SPointList *avoid) { - if(src->l.n < 1) oops(); + ssassert(!src->l.IsEmpty(), "Need at least one edge"); // Start with an arbitrary edge. - dest->l.Add(&(src->l.elem[0])); + dest->l.Add(src->l.First()); src->l.ClearTags(); - src->l.elem[0].tag = 1; + src->l.First()->tag = 1; bool added; do { added = false; // The start and finish of the current edge chain - Vector s = dest->l.elem[0].a, - f = dest->l.elem[dest->l.n - 1].b; + Vector s = dest->l.First()->a, + f = dest->l.Last()->b; // We can attach a new edge at the start or finish, as long as that // start or finish point isn't in the list of points to avoid. @@ -359,12 +436,12 @@ void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv, SCurve *sc = shell->curve.FindById(hc); if(sc->isExact && sc->exact.deg != 1) { double t; - sc->exact.ClosestPointTo(*pt, &t, false); + sc->exact.ClosestPointTo(*pt, &t, /*mustConverge=*/false); *pt = sc->exact.PointAt(t); ClosestPointTo(*pt, &muv); } else if(!sc->isExact) { SSurface *trimmedA = sc->GetSurfaceA(sha, shb), - *trimmedB = sc->GetSurfaceB(sha, shb); + *trimmedB = sc->GetSurfaceB(sha, shb); *pt = trimmedA->ClosestPointOnThisAndSurface(trimmedB, *pt); ClosestPointTo(*pt, &muv); } @@ -376,11 +453,17 @@ void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv, enxyz = (ab.Cross(*surfn)).WithMagnitude(SS.ChordTolMm()); // And based on that, compute the edge's inner normal in uv space. This // vector is perpendicular to the edge in xyz, but not necessarily in uv. - Vector tu, tv; + Vector tu, tv, tx, ty; TangentsAt(muv.x, muv.y, &tu, &tv); + Vector n = tu.Cross(tv); + // since tu and tv may not be orthogonal, use y in place of v, x in place of u. + // |y| = |v|sin(theta) where theta is the angle between tu and tv. + ty = n.Cross(tu).ScaledBy(1.0/tu.MagSquared()); + tx = tv.Cross(n).ScaledBy(1.0/tv.MagSquared()); + Point2d enuv; - enuv.x = enxyz.Dot(tu) / tu.MagSquared(); - enuv.y = enxyz.Dot(tv) / tv.MagSquared(); + enuv.x = enxyz.Dot(tx) / tx.MagSquared(); + enuv.y = enxyz.Dot(ty) / ty.MagSquared(); // Compute the inner and outer normals of this edge (within the srf), // in xyz space. These are not necessarily antiparallel, if the @@ -400,7 +483,8 @@ void SSurface::EdgeNormalsWithinSurface(Point2d auv, Point2d buv, SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, SShell *sha, SShell *shb, SShell *into, - int type) + SSurface::CombineAs type, + int dbg_index) { bool opA = (parent == sha); SShell *agnst = opA ? shb : sha; @@ -419,7 +503,7 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, ret.trim.Add(&stn); } - if(type == SShell::AS_DIFFERENCE && !opA) { + if(type == SSurface::CombineAs::DIFFERENCE && !opA) { // The second operand of a Boolean difference gets turned inside out ret.Reverse(); } @@ -428,7 +512,7 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, // be changed if we just flipped the surface normal, and we are using // the split curves (not the original curves). SEdgeList orig = {}; - ret.MakeEdgesInto(into, &orig, AS_UV); + ret.MakeEdgesInto(into, &orig, MakeAs::UV); ret.trim.Clear(); // which means that we can't necessarily use the old BSP... SBspUv *origBsp = SBspUv::From(&orig, &ret); @@ -437,46 +521,48 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, SEdgeList inter = {}; SSurface *ss; - for(ss = agnst->surface.First(); ss; ss = agnst->surface.NextAfter(ss)) { - SCurve *sc; - for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) { - if(sc->source != SCurve::FROM_INTERSECTION) continue; - if(opA) { - if(sc->surfA.v != h.v || sc->surfB.v != ss->h.v) continue; - } else { - if(sc->surfB.v != h.v || sc->surfA.v != ss->h.v) continue; - } - - int i; - for(i = 1; i < sc->pts.n; i++) { - Vector a = sc->pts.elem[i-1].p, - b = sc->pts.elem[i].p; - - Point2d auv, buv; - ss->ClosestPointTo(a, &(auv.x), &(auv.y)); - ss->ClosestPointTo(b, &(buv.x), &(buv.y)); - - int c = (ss->bsp) ? ss->bsp->ClassifyEdge(auv, buv, ss) : SBspUv::OUTSIDE; - if(c != SBspUv::OUTSIDE) { - Vector ta = Vector::From(0, 0, 0); - Vector tb = Vector::From(0, 0, 0); - ret.ClosestPointTo(a, &(ta.x), &(ta.y)); - ret.ClosestPointTo(b, &(tb.x), &(tb.y)); - - Vector tn = ret.NormalAt(ta.x, ta.y); - Vector sn = ss->NormalAt(auv.x, auv.y); - - // We are subtracting the portion of our surface that - // lies in the shell, so the in-plane edge normal should - // point opposite to the surface normal. - bool bkwds = true; - if((tn.Cross(b.Minus(a))).Dot(sn) < 0) bkwds = !bkwds; - if(type == SShell::AS_DIFFERENCE && !opA) bkwds = !bkwds; - if(bkwds) { - inter.AddEdge(tb, ta, sc->h.v, 1); - } else { - inter.AddEdge(ta, tb, sc->h.v, 0); - } + SCurve *sc; + for(sc = into->curve.First(); sc; sc = into->curve.NextAfter(sc)) { + if(sc->source != SCurve::Source::INTERSECTION) continue; + if(opA) { + if(sc->surfA != h) continue; + ss = shb->surface.FindById(sc->surfB); + } else { + if(sc->surfB != h) continue; + ss = sha->surface.FindById(sc->surfA); + } + int i; + for(i = 1; i < sc->pts.n; i++) { + Vector a = sc->pts[i-1].p, + b = sc->pts[i].p; + + Point2d auv, buv; + ss->ClosestPointTo(a, &(auv.x), &(auv.y)); + ss->ClosestPointTo(b, &(buv.x), &(buv.y)); + + SBspUv::Class c = (ss->bsp) ? ss->bsp->ClassifyEdge(auv, buv, ss) : SBspUv::Class::OUTSIDE; + if(c != SBspUv::Class::OUTSIDE) { + Vector ta = Vector::From(0, 0, 0); + Vector tb = Vector::From(0, 0, 0); + ret.ClosestPointTo(a, &(ta.x), &(ta.y)); + ret.ClosestPointTo(b, &(tb.x), &(tb.y)); + + Vector tn = ret.NormalAt(ta.x, ta.y); + Vector sn = ss->NormalAt(auv.x, auv.y); + + // We are subtracting the portion of our surface that + // lies in the shell, so the in-plane edge normal should + // point opposite to the surface normal. + bool bkwds = true; + if((tn.Cross(b.Minus(a))).Dot(sn) < 0) bkwds = !bkwds; + if((type == SSurface::CombineAs::DIFFERENCE && !opA) || + (type == SSurface::CombineAs::INTERSECTION)) { // Invert all newly created edges for intersection + bkwds = !bkwds; + } + if(bkwds) { + inter.AddEdge(tb, ta, sc->h.v, 1); + } else { + inter.AddEdge(ta, tb, sc->h.v, 0); } } } @@ -510,13 +596,13 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, // our original and intersecting edge lists. SEdgeList final = {}; - while(orig.l.n > 0) { + while(!orig.l.IsEmpty()) { SEdgeList chain = {}; FindChainAvoiding(&orig, &chain, &choosing); // Arbitrarily choose an edge within the chain to classify; they // should all be the same, though. - se = &(chain.l.elem[chain.l.n/2]); + se = &(chain.l[chain.l.n/2]); Point2d auv = (se->a).ProjectXy(), buv = (se->b).ProjectXy(); @@ -525,10 +611,10 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, ret.EdgeNormalsWithinSurface(auv, buv, &pt, &enin, &enout, &surfn, se->auxA, into, sha, shb); - int indir_shell, outdir_shell, indir_orig, outdir_orig; + SShell::Class indir_shell, outdir_shell, indir_orig, outdir_orig; - indir_orig = SShell::INSIDE; - outdir_orig = SShell::OUTSIDE; + indir_orig = SShell::Class::INSIDE; + outdir_orig = SShell::Class::OUTSIDE; agnst->ClassifyEdge(&indir_shell, &outdir_shell, ret.PointAt(auv), ret.PointAt(buv), pt, @@ -544,12 +630,12 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, chain.Clear(); } - while(inter.l.n > 0) { + while(!inter.l.IsEmpty()) { SEdgeList chain = {}; FindChainAvoiding(&inter, &chain, &choosing); // Any edge in the chain, same as above. - se = &(chain.l.elem[chain.l.n/2]); + se = &(chain.l[chain.l.n/2]); Point2d auv = (se->a).ProjectXy(), buv = (se->b).ProjectXy(); @@ -558,9 +644,9 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, ret.EdgeNormalsWithinSurface(auv, buv, &pt, &enin, &enout, &surfn, se->auxA, into, sha, shb); - int indir_shell, outdir_shell, indir_orig, outdir_orig; + SShell::Class indir_shell, outdir_shell, indir_orig, outdir_orig; - int c_this = (origBsp) ? origBsp->ClassifyEdge(auv, buv, &ret) : SBspUv::OUTSIDE; + SBspUv::Class c_this = (origBsp) ? origBsp->ClassifyEdge(auv, buv, &ret) : SBspUv::Class::OUTSIDE; TagByClassifiedEdge(c_this, &indir_orig, &outdir_orig); agnst->ClassifyEdge(&indir_shell, &outdir_shell, @@ -581,16 +667,18 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, // we can get duplicate edges if our surface intersects the other shell // at an edge, so that both surfaces intersect coincident (and both // generate an intersection edge). - final.CullExtraneousEdges(); + final.CullExtraneousEdges(/*both=*/true); // Use our reassembled edges to trim the new surface. - ret.TrimFromEdgeList(&final, true); + ret.TrimFromEdgeList(&final, /*asUv=*/true); SPolygon poly = {}; final.l.ClearTags(); - if(!final.AssemblePolygon(&poly, NULL, true)) { + if(!final.AssemblePolygon(&poly, NULL, /*keepDir=*/true)) +#pragma omp critical + { into->booleanFailed = true; - dbp("failed: I=%d, avoid=%d", I, choosing.l.n); + dbp("failed: I=%d, avoid=%d", I+dbg_index, choosing.l.n); DEBUGEDGELIST(&final, &ret); } poly.Clear(); @@ -602,21 +690,27 @@ SSurface SSurface::MakeCopyTrimAgainst(SShell *parent, return ret; } -void SShell::CopySurfacesTrimAgainst(SShell *sha, SShell *shb, SShell *into, - int type) -{ - SSurface *ss; - for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { - SSurface ssn; - ssn = ss->MakeCopyTrimAgainst(this, sha, shb, into, type); - ss->newH = into->surface.AddAndAssignId(&ssn); - I++; +void SShell::CopySurfacesTrimAgainst(SShell *sha, SShell *shb, SShell *into, SSurface::CombineAs type) { + std::vector ssn(surface.n); +#pragma omp parallel for + for (int i = 0; i < surface.n; i++) + { + SSurface *ss = &surface[i]; + ssn[i] = ss->MakeCopyTrimAgainst(this, sha, shb, into, type, i); + } + + for (int i = 0; i < surface.n; i++) + { + surface[i].newH = into->surface.AddAndAssignId(&ssn[i]); } + I += surface.n; } void SShell::MakeIntersectionCurvesAgainst(SShell *agnst, SShell *into) { - SSurface *sa; - for(sa = surface.First(); sa; sa = surface.NextAfter(sa)) { +#pragma omp parallel for + for(int i = 0; i< surface.n; i++) { + SSurface *sa = &surface[i]; + SSurface *sb; for(sb = agnst->surface.First(); sb; sb = agnst->surface.NextAfter(sb)){ // Intersect every surface from our shell against every surface @@ -627,7 +721,7 @@ void SShell::MakeIntersectionCurvesAgainst(SShell *agnst, SShell *into) { } } -void SShell::CleanupAfterBoolean(void) { +void SShell::CleanupAfterBoolean() { SSurface *ss; for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { ss->edges.Clear(); @@ -664,12 +758,13 @@ void SShell::MakeFromAssemblyOf(SShell *a, SShell *b) { // First, copy over all the curves. Note which shell (a or b) each curve // came from, but assign it a new ID. + curve.ReserveMore(a->curve.n + b->curve.n); SCurve *c, cn; for(i = 0; i < 2; i++) { ab = (i == 0) ? a : b; for(c = ab->curve.First(); c; c = ab->curve.NextAfter(c)) { cn = SCurve::FromTransformationOf(c, t, q, 1.0); - cn.source = (i == 0) ? SCurve::FROM_A : SCurve::FROM_B; + cn.source = (i == 0) ? SCurve::Source::A : SCurve::Source::B; // surfA and surfB are wrong now, and we can't fix them until // we've assigned IDs to the surfaces. So we'll get that later. c->newH = curve.AddAndAssignId(&cn); @@ -677,11 +772,12 @@ void SShell::MakeFromAssemblyOf(SShell *a, SShell *b) { } // Likewise copy over all the surfaces. + surface.ReserveMore(a->surface.n + b->surface.n); SSurface *s, sn; for(i = 0; i < 2; i++) { ab = (i == 0) ? a : b; for(s = ab->surface.First(); s; s = ab->surface.NextAfter(s)) { - sn = SSurface::FromTransformationOf(s, t, q, 1.0, true); + sn = SSurface::FromTransformationOf(s, t, q, 1.0, /*includingTrims=*/true); // All the trim curve IDs get rewritten; we know the new handles // to the curves since we recorded them in the previous step. STrimBy *stb; @@ -697,7 +793,7 @@ void SShell::MakeFromAssemblyOf(SShell *a, SShell *b) { RewriteSurfaceHandlesForCurves(a, b); } -void SShell::MakeFromBoolean(SShell *a, SShell *b, int type) { +void SShell::MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type) { booleanFailed = false; a->MakeClassifyingBsps(NULL); @@ -706,8 +802,8 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, int type) { // Copy over all the original curves, splitting them so that a // piecwise linear segment never crosses a surface from the other // shell. - a->CopyCurvesSplitAgainst(true, b, this); - b->CopyCurvesSplitAgainst(false, a, this); + a->CopyCurvesSplitAgainst(/*opA=*/true, b, this); + b->CopyCurvesSplitAgainst(/*opA=*/false, a, this); // Generate the intersection curves for each surface in A against all // the surfaces in B (which is all of the intersection curves). @@ -716,7 +812,7 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, int type) { SCurve *sc; for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) { SSurface *srfA = sc->GetSurfaceA(a, b), - *srfB = sc->GetSurfaceB(a, b); + *srfB = sc->GetSurfaceB(a, b); sc->RemoveShortSegments(srfA, srfB); } @@ -729,7 +825,7 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, int type) { a->MakeClassifyingBsps(this); b->MakeClassifyingBsps(this); - if(b->surface.n == 0 || a->surface.n == 0) { + if(b->surface.IsEmpty() || a->surface.IsEmpty()) { I = 1000000; } else { I = 0; @@ -752,39 +848,27 @@ void SShell::MakeFromBoolean(SShell *a, SShell *b, int type) { // All of the BSP routines that we use to perform and accelerate polygon ops. //----------------------------------------------------------------------------- void SShell::MakeClassifyingBsps(SShell *useCurvesFrom) { - SSurface *ss; - for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { - ss->MakeClassifyingBsp(this, useCurvesFrom); +#pragma omp parallel for + for(int i = 0; ia).Minus(a->b).Magnitude(), - lb = (b->a).Minus(b->b).Magnitude(); - - // Sort in descending order, longest first. This improves numerical - // stability for the normals. - return (la < lb) ? 1 : -1; -} SBspUv *SBspUv::From(SEdgeList *el, SSurface *srf) { SEdgeList work = {}; @@ -792,8 +876,12 @@ SBspUv *SBspUv::From(SEdgeList *el, SSurface *srf) { for(se = el->l.First(); se; se = el->l.NextAfter(se)) { work.AddEdge(se->a, se->b, se->auxA, se->auxB); } - qsort(work.l.elem, work.l.n, sizeof(work.l.elem[0]), ByLength); - + std::sort(work.l.begin(), work.l.end(), [](SEdge const &a, SEdge const &b) { + double la = (a.a).Minus(a.b).Magnitude(), lb = (b.a).Minus(b.b).Magnitude(); + // Sort in descending order, longest first. This improves numerical + // stability for the normals. + return la > lb; + }); SBspUv *bsp = NULL; for(se = work.l.First(); se; se = work.l.NextAfter(se)) { bsp = InsertOrCreateEdge(bsp, (se->a).ProjectXy(), (se->b).ProjectXy(), srf); @@ -812,7 +900,8 @@ SBspUv *SBspUv::From(SEdgeList *el, SSurface *srf) { // time we care about exact correctness is when we're very close to the line, // which is when the linearization is accurate. //----------------------------------------------------------------------------- -void SBspUv::ScalePoints(Point2d *pt, Point2d *a, Point2d *b, SSurface *srf) { + +void SBspUv::ScalePoints(Point2d *pt, Point2d *a, Point2d *b, SSurface *srf) const { Vector tu, tv; srf->TangentsAt(pt->x, pt->y, &tu, &tv); double mu = tu.Magnitude(), mv = tv.Magnitude(); @@ -821,8 +910,9 @@ void SBspUv::ScalePoints(Point2d *pt, Point2d *a, Point2d *b, SSurface *srf) { a ->x *= mu; a ->y *= mv; b ->x *= mu; b ->y *= mv; } + double SBspUv::ScaledSignedDistanceToLine(Point2d pt, Point2d a, Point2d b, - SSurface *srf) + SSurface *srf) const { ScalePoints(&pt, &a, &b, srf); @@ -831,15 +921,16 @@ double SBspUv::ScaledSignedDistanceToLine(Point2d pt, Point2d a, Point2d b, return pt.Dot(n) - d; } -double SBspUv::ScaledDistanceToLine(Point2d pt, Point2d a, Point2d b, bool seg, - SSurface *srf) + +double SBspUv::ScaledDistanceToLine(Point2d pt, Point2d a, Point2d b, bool asSegment, + SSurface *srf) const { ScalePoints(&pt, &a, &b, srf); - return pt.DistanceToLine(a, b, seg); + return pt.DistanceToLine(a, b, asSegment); } -SBspUv *SBspUv::InsertOrCreateEdge(SBspUv *where, const Point2d &ea, const Point2d &eb, SSurface *srf) { +SBspUv *SBspUv::InsertOrCreateEdge(SBspUv *where, Point2d ea, Point2d eb, SSurface *srf) { if(where == NULL) { SBspUv *ret = Alloc(); ret->a = ea; @@ -862,14 +953,14 @@ void SBspUv::InsertEdge(Point2d ea, Point2d eb, SSurface *srf) { m->more = more; more = m; } else if(fabs(dea) < LENGTH_EPS) { - // Point A lies on this lie, but point B does not + // Point A lies on this line, but point B does not if(deb > 0) { pos = InsertOrCreateEdge(pos, ea, eb, srf); } else { neg = InsertOrCreateEdge(neg, ea, eb, srf); } } else if(fabs(deb) < LENGTH_EPS) { - // Point B lies on this lie, but point A does not + // Point B lies on this line, but point A does not if(dea > 0) { pos = InsertOrCreateEdge(pos, ea, eb, srf); } else { @@ -896,44 +987,43 @@ void SBspUv::InsertEdge(Point2d ea, Point2d eb, SSurface *srf) { return; } -int SBspUv::ClassifyPoint(Point2d p, Point2d eb, SSurface *srf) { - +SBspUv::Class SBspUv::ClassifyPoint(Point2d p, Point2d eb, SSurface *srf) const { double dp = ScaledSignedDistanceToLine(p, a, b, srf); if(fabs(dp) < LENGTH_EPS) { - SBspUv *f = this; + const SBspUv *f = this; while(f) { Point2d ba = (f->b).Minus(f->a); - if(ScaledDistanceToLine(p, f->a, ba, true, srf) < LENGTH_EPS) { - if(ScaledDistanceToLine(eb, f->a, ba, false, srf) < LENGTH_EPS){ + if(ScaledDistanceToLine(p, f->a, ba, /*asSegment=*/true, srf) < LENGTH_EPS) { + if(ScaledDistanceToLine(eb, f->a, ba, /*asSegment=*/false, srf) < LENGTH_EPS){ if(ba.Dot(eb.Minus(p)) > 0) { - return EDGE_PARALLEL; + return Class::EDGE_PARALLEL; } else { - return EDGE_ANTIPARALLEL; + return Class::EDGE_ANTIPARALLEL; } } else { - return EDGE_OTHER; + return Class::EDGE_OTHER; } } f = f->more; } // Pick arbitrarily which side to send it down, doesn't matter - int c1 = neg ? neg->ClassifyPoint(p, eb, srf) : OUTSIDE; - int c2 = pos ? pos->ClassifyPoint(p, eb, srf) : INSIDE; + Class c1 = neg ? neg->ClassifyPoint(p, eb, srf) : Class::OUTSIDE; + Class c2 = pos ? pos->ClassifyPoint(p, eb, srf) : Class::INSIDE; if(c1 != c2) { dbp("MISMATCH: %d %d %08x %08x", c1, c2, neg, pos); } return c1; } else if(dp > 0) { - return pos ? pos->ClassifyPoint(p, eb, srf) : INSIDE; + return pos ? pos->ClassifyPoint(p, eb, srf) : Class::INSIDE; } else { - return neg ? neg->ClassifyPoint(p, eb, srf) : OUTSIDE; + return neg ? neg->ClassifyPoint(p, eb, srf) : Class::OUTSIDE; } } -int SBspUv::ClassifyEdge(Point2d ea, Point2d eb, SSurface *srf) { - int ret = ClassifyPoint((ea.Plus(eb)).ScaledBy(0.5), eb, srf); - if(ret == EDGE_OTHER) { +SBspUv::Class SBspUv::ClassifyEdge(Point2d ea, Point2d eb, SSurface *srf) const { + SBspUv::Class ret = ClassifyPoint((ea.Plus(eb)).ScaledBy(0.5), eb, srf); + if(ret == Class::EDGE_OTHER) { // Perhaps the edge is tangent at its midpoint (and we screwed up // somewhere earlier and failed to split it); try a different // point on the edge. @@ -942,14 +1032,14 @@ int SBspUv::ClassifyEdge(Point2d ea, Point2d eb, SSurface *srf) { return ret; } -double SBspUv::MinimumDistanceToEdge(Point2d p, SSurface *srf) { +double SBspUv::MinimumDistanceToEdge(Point2d p, SSurface *srf) const { double dn = (neg) ? neg->MinimumDistanceToEdge(p, srf) : VERY_POSITIVE; double dp = (pos) ? pos->MinimumDistanceToEdge(p, srf) : VERY_POSITIVE; Point2d as = a, bs = b; ScalePoints(&p, &as, &bs, srf); - double d = p.DistanceToLine(as, bs.Minus(as), true); + double d = p.DistanceToLine(as, bs.Minus(as), /*asSegment=*/true); return min(d, min(dn, dp)); } diff --git a/src/srf/curve.cpp b/src/srf/curve.cpp index ba54afc..55496b6 100644 --- a/src/srf/curve.cpp +++ b/src/srf/curve.cpp @@ -60,15 +60,15 @@ SBezier SBezier::From(Vector p0, Vector p1, Vector p2, Vector p3) { p3.Project4d()); } -Vector SBezier::Start(void) { +Vector SBezier::Start() const { return ctrl[0]; } -Vector SBezier::Finish(void) { +Vector SBezier::Finish() const { return ctrl[deg]; } -void SBezier::Reverse(void) { +void SBezier::Reverse() { int i; for(i = 0; i < (deg+1)/2; i++) { swap(ctrl[i], ctrl[deg-i]); @@ -84,7 +84,7 @@ void SBezier::ScaleSelfBy(double s) { } void SBezier::GetBoundingProjd(Vector u, Vector orig, - double *umin, double *umax) + double *umin, double *umax) const { int i; for(i = 0; i <= deg; i++) { @@ -94,7 +94,7 @@ void SBezier::GetBoundingProjd(Vector u, Vector orig, } } -SBezier SBezier::TransformedBy(Vector t, Quaternion q, double scale) { +SBezier SBezier::TransformedBy(Vector t, Quaternion q, double scale) const { SBezier ret = *this; int i; for(i = 0; i <= deg; i++) { @@ -108,7 +108,7 @@ SBezier SBezier::TransformedBy(Vector t, Quaternion q, double scale) { // Does this curve lie entirely within the specified plane? It does if all // the control points lie in that plane. //----------------------------------------------------------------------------- -bool SBezier::IsInPlane(Vector n, double d) { +bool SBezier::IsInPlane(Vector n, double d) const { int i; for(i = 0; i <= deg; i++) { if(fabs((ctrl[i]).Dot(n) - d) > LENGTH_EPS) { @@ -122,7 +122,7 @@ bool SBezier::IsInPlane(Vector n, double d) { // Is this Bezier exactly the arc of a circle, projected along the specified // axis? If yes, return that circle's center and radius. //----------------------------------------------------------------------------- -bool SBezier::IsCircle(Vector axis, Vector *center, double *r) { +bool SBezier::IsCircle(Vector axis, Vector *center, double *r) const { if(deg != 2) return false; if(ctrl[1].DistanceToLine(ctrl[0], ctrl[2].Minus(ctrl[0])) < LENGTH_EPS) { @@ -170,7 +170,7 @@ bool SBezier::IsCircle(Vector axis, Vector *center, double *r) { return true; } -bool SBezier::IsRational(void) { +bool SBezier::IsRational() const { int i; for(i = 0; i <= deg; i++) { if(fabs(weight[i] - 1) > LENGTH_EPS) return true; @@ -183,7 +183,7 @@ bool SBezier::IsRational(void) { // the new weights as required. //----------------------------------------------------------------------------- SBezier SBezier::InPerspective(Vector u, Vector v, Vector n, - Vector origin, double cameraTan) + Vector origin, double cameraTan) const { Quaternion q = Quaternion::From(u, v); q = q.Inverse(); @@ -206,7 +206,7 @@ SBezier SBezier::InPerspective(Vector u, Vector v, Vector n, return ret; } -bool SBezier::Equals(SBezier *b) { +bool SBezier::Equals(SBezier *b) const { // We just test of identical degree and control points, even though two // curves could still be coincident (even sharing endpoints). if(deg != b->deg) return false; @@ -218,7 +218,7 @@ bool SBezier::Equals(SBezier *b) { return true; } -void SBezierList::Clear(void) { +void SBezierList::Clear() { l.Clear(); } @@ -231,23 +231,24 @@ void SBezierList::ScaleSelfBy(double s) { //----------------------------------------------------------------------------- // If our list contains multiple identical Beziers (in either forward or -// reverse order), then cull them. +// reverse order), then cull them. If both is true, both beziers are removed. +// Otherwise only one of them is removed. //----------------------------------------------------------------------------- -void SBezierList::CullIdenticalBeziers(void) { +void SBezierList::CullIdenticalBeziers(bool both) { int i, j; l.ClearTags(); for(i = 0; i < l.n; i++) { - SBezier *bi = &(l.elem[i]), bir; + SBezier *bi = &(l[i]), bir; bir = *bi; bir.Reverse(); for(j = i + 1; j < l.n; j++) { - SBezier *bj = &(l.elem[j]); + SBezier *bj = &(l[j]); if(bj->Equals(bi) || bj->Equals(&bir)) { - bi->tag = 1; + if (both) bi->tag = 1; bj->tag = 1; } } @@ -262,15 +263,14 @@ void SBezierList::CullIdenticalBeziers(void) { // curves. So this will screw up on tangencies and stuff, but otherwise should // be fine. //----------------------------------------------------------------------------- -void SBezierList::AllIntersectionsWith(SBezierList *sblb, SPointList *spl) { - SBezier *sba, *sbb; - for(sba = l.First(); sba; sba = l.NextAfter(sba)) { - for(sbb = sblb->l.First(); sbb; sbb = sblb->l.NextAfter(sbb)) { +void SBezierList::AllIntersectionsWith(SBezierList *sblb, SPointList *spl) const { + for(const SBezier *sba = l.First(); sba; sba = l.NextAfter(sba)) { + for(const SBezier *sbb = sblb->l.First(); sbb; sbb = sblb->l.NextAfter(sbb)) { sbb->AllIntersectionsWith(sba, spl); } } } -void SBezier::AllIntersectionsWith(SBezier *sbb, SPointList *spl) { +void SBezier::AllIntersectionsWith(const SBezier *sbb, SPointList *spl) const { SPointList splRaw = {}; SEdgeList sea, seb; sea = {}; @@ -304,16 +304,15 @@ void SBezier::AllIntersectionsWith(SBezier *sbb, SPointList *spl) { // Returns true if all the curves are coplanar, otherwise false. //----------------------------------------------------------------------------- bool SBezierList::GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v, - Vector *notCoplanarAt) + Vector *notCoplanarAt) const { Vector pt, ptFar, ptOffLine, dp, n; double farMax, offLineMax; int i; - SBezier *sb; // Get any point on any Bezier; or an arbitrary point if list is empty. - if(l.n > 0) { - pt = l.elem[0].Start(); + if(!l.IsEmpty()) { + pt = l[0].Start(); } else { pt = Vector::From(0, 0, 0); } @@ -321,7 +320,7 @@ bool SBezierList::GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v, // Get the point farthest from our arbitrary point. farMax = VERY_NEGATIVE; - for(sb = l.First(); sb; sb = l.NextAfter(sb)) { + for(const SBezier *sb = l.First(); sb; sb = l.NextAfter(sb)) { for(i = 0; i <= sb->deg; i++) { double m = (pt.Minus(sb->ctrl[i])).Magnitude(); if(m > farMax) { @@ -341,7 +340,7 @@ bool SBezierList::GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v, // Get the point farthest from the line between pt and ptFar dp = ptFar.Minus(pt); offLineMax = VERY_NEGATIVE; - for(sb = l.First(); sb; sb = l.NextAfter(sb)) { + for(const SBezier *sb = l.First(); sb; sb = l.NextAfter(sb)) { for(i = 0; i <= sb->deg; i++) { double m = (sb->ctrl[i]).DistanceToLine(pt, dp); if(m > offLineMax) { @@ -369,7 +368,7 @@ bool SBezierList::GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v, n = u->Cross(*v); n = n.WithMagnitude(1); double d = p->Dot(n); - for(sb = l.First(); sb; sb = l.NextAfter(sb)) { + for(const SBezier *sb = l.First(); sb; sb = l.NextAfter(sb)) { for(i = 0; i <= sb->deg; i++) { if(fabs(n.Dot(sb->ctrl[i]) - d) > LENGTH_EPS) { if(notCoplanarAt) *notCoplanarAt = sb->ctrl[i]; @@ -394,7 +393,7 @@ SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl, if(sbl->l.n < 1) return loop; sbl->l.ClearTags(); - SBezier *first = &(sbl->l.elem[0]); + SBezier *first = &(sbl->l[0]); first->tag = 1; loop.l.Add(first); Vector start = first->Start(); @@ -403,11 +402,11 @@ SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl, sbl->l.RemoveTagged(); - while(sbl->l.n > 0 && !hanging.Equals(start)) { + while(!sbl->l.IsEmpty() && !hanging.Equals(start)) { int i; bool foundNext = false; for(i = 0; i < sbl->l.n; i++) { - SBezier *test = &(sbl->l.elem[i]); + SBezier *test = &(sbl->l[i]); if((test->Finish()).Equals(hanging) && test->auxA == auxA) { test->Reverse(); @@ -443,7 +442,7 @@ SBezierLoop SBezierLoop::FromCurves(SBezierList *sbl, return loop; } -void SBezierLoop::Reverse(void) { +void SBezierLoop::Reverse() { l.Reverse(); SBezier *sb; for(sb = l.First(); sb; sb = l.NextAfter(sb)) { @@ -454,17 +453,15 @@ void SBezierLoop::Reverse(void) { } void SBezierLoop::GetBoundingProjd(Vector u, Vector orig, - double *umin, double *umax) + double *umin, double *umax) const { - SBezier *sb; - for(sb = l.First(); sb; sb = l.NextAfter(sb)) { + for(const SBezier *sb = l.First(); sb; sb = l.NextAfter(sb)) { sb->GetBoundingProjd(u, orig, umin, umax); } } -void SBezierLoop::MakePwlInto(SContour *sc, double chordTol) { - SBezier *sb; - for(sb = l.First(); sb; sb = l.NextAfter(sb)) { +void SBezierLoop::MakePwlInto(SContour *sc, double chordTol) const { + for(const SBezier *sb = l.First(); sb; sb = l.NextAfter(sb)) { sb->MakePwlInto(sc, chordTol); // Avoid double points at join between Beziers; except that // first and last points should be identical. @@ -473,15 +470,15 @@ void SBezierLoop::MakePwlInto(SContour *sc, double chordTol) { } } // Ensure that it's exactly closed, not just within a numerical tolerance. - if((sc->l.elem[sc->l.n - 1].p).Equals(sc->l.elem[0].p)) { - sc->l.elem[sc->l.n - 1] = sc->l.elem[0]; + if((sc->l.Last()->p).Equals(sc->l.First()->p)) { + *sc->l.Last() = *sc->l.First(); } } -bool SBezierLoop::IsClosed(void) { - if(l.n < 1) return false; - Vector s = l.elem[0].Start(), - f = l.elem[l.n-1].Finish(); +bool SBezierLoop::IsClosed() const { + if(l.IsEmpty()) return false; + Vector s = l.First()->Start(), + f = l.Last()->Finish(); return s.Equals(f); } @@ -495,12 +492,12 @@ bool SBezierLoop::IsClosed(void) { SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly, double chordTol, bool *allClosed, SEdge *errorAt, - SBezierList *openContours) + SBezierLoopSet *openContours) { SBezierLoopSet ret = {}; *allClosed = true; - while(sbl->l.n > 0) { + while(!sbl->l.IsEmpty()) { bool thisClosed; SBezierLoop loop; loop = SBezierLoop::FromCurves(sbl, &thisClosed, errorAt); @@ -508,16 +505,14 @@ SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly, // Record open loops in a separate list, if requested. *allClosed = false; if(openContours) { - SBezier *sb; - for(sb = loop.l.First(); sb; sb = loop.l.NextAfter(sb)) { - openContours->l.Add(sb); - } + openContours->l.Add(&loop); + } else { + loop.Clear(); } - loop.Clear(); } else { ret.l.Add(&loop); poly->AddEmptyContour(); - loop.MakePwlInto(&(poly->l.elem[poly->l.n-1]), chordTol); + loop.MakePwlInto(poly->l.Last(), chordTol); } } @@ -533,30 +528,39 @@ SBezierLoopSet SBezierLoopSet::From(SBezierList *sbl, SPolygon *poly, } void SBezierLoopSet::GetBoundingProjd(Vector u, Vector orig, - double *umin, double *umax) + double *umin, double *umax) const { - SBezierLoop *sbl; - for(sbl = l.First(); sbl; sbl = l.NextAfter(sbl)) { + for(const SBezierLoop *sbl = l.First(); sbl; sbl = l.NextAfter(sbl)) { sbl->GetBoundingProjd(u, orig, umin, umax); } } +double SBezierLoopSet::SignedArea() { + if(EXACT(area == 0.0)) { + SPolygon sp = {}; + MakePwlInto(&sp); + sp.normal = sp.ComputeNormal(); + area = sp.SignedArea(); + sp.Clear(); + } + return area; +} + //----------------------------------------------------------------------------- // Convert all the Beziers into piecewise linear form, and assemble that into // a polygon, one contour per loop. //----------------------------------------------------------------------------- -void SBezierLoopSet::MakePwlInto(SPolygon *sp) { - SBezierLoop *sbl; - for(sbl = l.First(); sbl; sbl = l.NextAfter(sbl)) { +void SBezierLoopSet::MakePwlInto(SPolygon *sp) const { + for(const SBezierLoop *sbl = l.First(); sbl; sbl = l.NextAfter(sbl)) { sp->AddEmptyContour(); - sbl->MakePwlInto(&(sp->l.elem[sp->l.n - 1])); + sbl->MakePwlInto(sp->l.Last()); } } -void SBezierLoopSet::Clear(void) { +void SBezierLoopSet::Clear() { int i; for(i = 0; i < l.n; i++) { - (l.elem[i]).Clear(); + (l[i]).Clear(); } l.Clear(); } @@ -571,7 +575,7 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, double chordTol, bool *allClosed, SEdge *notClosedAt, bool *allCoplanar, Vector *notCoplanarAt, - SBezierList *openContours) + SBezierLoopSet *openContours) { SSurface srfPlane; if(!srfuv) { @@ -584,7 +588,9 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, if(openContours) { SBezier *sb; for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { - openContours->l.Add(sb); + SBezierLoop sbl={}; + sbl.l.Add(sb); + openContours->l.Add(&sbl); } } return; @@ -612,7 +618,7 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, for(pt = sc->l.First(); pt; pt = sc->l.NextAfter(pt)) { double u, v; srfuv->ClosestPointTo(pt->p, &u, &v); - spuv.l.elem[spuv.l.n - 1].AddPoint(Vector::From(u, v, 0)); + spuv.l.Last()->AddPoint(Vector::From(u, v, 0)); } } spuv.normal = Vector::From(0, 0, 1); // must be, since it's in xy plane now @@ -624,8 +630,8 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, // works for curved surfaces too (important for STEP export). spuv.FixContourDirections(); for(i = 0; i < spuv.l.n; i++) { - SContour *contour = &(spuv.l.elem[i]); - SBezierLoop *bl = &(sbls.l.elem[i]); + SContour *contour = &(spuv.l[i]); + SBezierLoop *bl = &(sbls.l[i]); if(contour->tag) { // This contour got reversed in the polygon to make the directions // consistent, so the same must be necessary for the Bezier loop. @@ -642,7 +648,7 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, while(loopsRemaining) { loopsRemaining = false; for(i = 0; i < sbls.l.n; i++) { - SBezierLoop *loop = &(sbls.l.elem[i]); + SBezierLoop *loop = &(sbls.l[i]); if(loop->tag != OUTER_LOOP) continue; // Check if this contour contains any outer loops; if it does, then @@ -650,12 +656,12 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, // will steal their holes, since their holes also lie inside this // contour. for(j = 0; j < sbls.l.n; j++) { - SBezierLoop *outer = &(sbls.l.elem[j]); + SBezierLoop *outer = &(sbls.l[j]); if(i == j) continue; if(outer->tag != OUTER_LOOP) continue; - Vector p = spuv.l.elem[j].AnyEdgeMidpoint(); - if(spuv.l.elem[i].ContainsPointProjdToNormal(spuv.normal, p)) { + Vector p = spuv.l[j].AnyEdgeMidpoint(); + if(spuv.l[i].ContainsPointProjdToNormal(spuv.normal, p)) { break; } } @@ -669,16 +675,16 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, loop->tag = USED_LOOP; outerAndInners.l.Add(loop); int auxA = 0; - if(loop->l.n > 0) auxA = loop->l.elem[0].auxA; + if(loop->l.n > 0) auxA = loop->l[0].auxA; for(j = 0; j < sbls.l.n; j++) { - SBezierLoop *inner = &(sbls.l.elem[j]); + SBezierLoop *inner = &(sbls.l[j]); if(inner->tag != INNER_LOOP) continue; if(inner->l.n < 1) continue; - if(inner->l.elem[0].auxA != auxA) continue; + if(inner->l[0].auxA != auxA) continue; - Vector p = spuv.l.elem[j].AnyEdgeMidpoint(); - if(spuv.l.elem[i].ContainsPointProjdToNormal(spuv.normal, p)) { + Vector p = spuv.l[j].AnyEdgeMidpoint(); + if(spuv.l[i].ContainsPointProjdToNormal(spuv.normal, p)) { outerAndInners.l.Add(inner); inner->tag = USED_LOOP; } @@ -696,16 +702,14 @@ void SBezierLoopSetSet::FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, // to screw up on that stuff. So just add them onto the open curve list. // Very ugly, but better than losing curves. for(i = 0; i < sbls.l.n; i++) { - SBezierLoop *loop = &(sbls.l.elem[i]); + SBezierLoop *loop = &(sbls.l[i]); if(loop->tag == USED_LOOP) continue; if(openContours) { - SBezier *sb; - for(sb = loop->l.First(); sb; sb = loop->l.NextAfter(sb)) { - openContours->l.Add(sb); - } + openContours->l.Add(loop); + } else { + loop->Clear(); } - loop->Clear(); // but don't free the used loops, since we shallow-copied them to // ourself } @@ -724,7 +728,7 @@ void SBezierLoopSetSet::AddOpenPath(SBezier *sb) { l.Add(&sbls); } -void SBezierLoopSetSet::Clear(void) { +void SBezierLoopSetSet::Clear() { SBezierLoopSet *sbls; for(sbls = l.First(); sbls; sbls = l.NextAfter(sbls)) { sbls->Clear(); @@ -732,11 +736,14 @@ void SBezierLoopSetSet::Clear(void) { l.Clear(); } -SCurve SCurve::FromTransformationOf(SCurve *a, - Vector t, Quaternion q, double scale) +SCurve SCurve::FromTransformationOf(SCurve *a, Vector t, + Quaternion q, double scale) { - SCurve ret = {}; + bool needRotate = !EXACT(q.vx == 0.0 && q.vy == 0.0 && q.vz == 0.0 && q.w == 1.0); + bool needTranslate = !EXACT(t.x == 0.0 && t.y == 0.0 && t.z == 0.0); + bool needScale = !EXACT(scale == 1.0); + SCurve ret = {}; ret.h = a->h; ret.isExact = a->isExact; ret.exact = (a->exact).TransformedBy(t, q, scale); @@ -744,37 +751,45 @@ SCurve SCurve::FromTransformationOf(SCurve *a, ret.surfB = a->surfB; SCurvePt *p; + ret.pts.ReserveMore(a->pts.n); for(p = a->pts.First(); p; p = a->pts.NextAfter(p)) { SCurvePt pp = *p; - pp.p = (pp.p).ScaledBy(scale); - pp.p = (q.Rotate(pp.p)).Plus(t); + if(needScale) { + pp.p = (pp.p).ScaledBy(scale); + } + if(needRotate) { + pp.p = q.Rotate(pp.p); + } + if(needTranslate) { + pp.p = pp.p.Plus(t); + } ret.pts.Add(&pp); } return ret; } -void SCurve::Clear(void) { +void SCurve::Clear() { pts.Clear(); } -SSurface *SCurve::GetSurfaceA(SShell *a, SShell *b) { - if(source == FROM_A) { +SSurface *SCurve::GetSurfaceA(SShell *a, SShell *b) const { + if(source == Source::A) { return a->surface.FindById(surfA); - } else if(source == FROM_B) { + } else if(source == Source::B) { return b->surface.FindById(surfA); - } else if(source == FROM_INTERSECTION) { + } else if(source == Source::INTERSECTION) { return a->surface.FindById(surfA); - } else oops(); + } else ssassert(false, "Unexpected curve source"); } -SSurface *SCurve::GetSurfaceB(SShell *a, SShell *b) { - if(source == FROM_A) { +SSurface *SCurve::GetSurfaceB(SShell *a, SShell *b) const { + if(source == Source::A) { return a->surface.FindById(surfB); - } else if(source == FROM_B) { + } else if(source == Source::B) { return b->surface.FindById(surfB); - } else if(source == FROM_INTERSECTION) { + } else if(source == Source::INTERSECTION) { return b->surface.FindById(surfB); - } else oops(); + } else ssassert(false, "Unexpected curve source"); } //----------------------------------------------------------------------------- @@ -784,20 +799,37 @@ SSurface *SCurve::GetSurfaceB(SShell *a, SShell *b) { // stuff in the Booleans. So remove them. //----------------------------------------------------------------------------- void SCurve::RemoveShortSegments(SSurface *srfA, SSurface *srfB) { - // Three, not two; curves are pwl'd to at least two edges (three points) - // even if not necessary, to avoid square holes. - if(pts.n <= 3) return; + if(pts.n <= 2) return; pts.ClearTags(); - Vector prev = pts.elem[0].p; + Vector prev = pts[0].p; + double tprev = 0; + double t = 0; + double tnext = 0; + int i, a; for(i = 1; i < pts.n - 1; i++) { - SCurvePt *sct = &(pts.elem[i]), - *scn = &(pts.elem[i+1]); + SCurvePt *sct = &(pts[i]), + *scn = &(pts[i+1]); + if(sct->vertex) { prev = sct->p; continue; } + + // if the curve is exact and points are >0.05 appart wrt t, point is there + // deliberately regardless of chord tolerance (ex: small circles) + tprev = t = tnext = 0; + if (isExact) { + exact.ClosestPointTo(prev, &tprev, /*mustconverge=*/ true); + exact.ClosestPointTo(sct->p, &t, /*mustconverge=*/ true); + exact.ClosestPointTo(scn->p, &tnext, /*mustconverge=*/ true); + } + if ( (t - tprev > 0.05) && (tnext - t > 0.05) ) { + prev = sct->p; + continue; + } + bool mustKeep = false; // We must check against both surfaces; the piecewise linear edge @@ -811,7 +843,7 @@ void SCurve::RemoveShortSegments(SSurface *srfA, SSurface *srfB) { srf->ClosestPointTo(prev, &(puv.x), &(puv.y)); srf->ClosestPointTo(scn->p, &(nuv.x), &(nuv.y)); - if(srf->ChordToleranceForEdge(nuv, puv) > SS.ChordTolMm()) { + if(srf->ChordToleranceForEdge(nuv, puv) > SS.ChordTolMm() ) { mustKeep = true; } } @@ -834,12 +866,12 @@ STrimBy STrimBy::EntireCurve(SShell *shell, hSCurve hsc, bool backwards) { SCurve *sc = shell->curve.FindById(hsc); if(backwards) { - stb.finish = sc->pts.elem[0].p; - stb.start = sc->pts.elem[sc->pts.n - 1].p; + stb.finish = sc->pts[0].p; + stb.start = sc->pts.Last()->p; stb.backwards = true; } else { - stb.start = sc->pts.elem[0].p; - stb.finish = sc->pts.elem[sc->pts.n - 1].p; + stb.start = sc->pts[0].p; + stb.finish = sc->pts.Last()->p; stb.backwards = false; } diff --git a/src/srf/merge.cpp b/src/srf/merge.cpp index 487fb92..a91a307 100644 --- a/src/srf/merge.cpp +++ b/src/srf/merge.cpp @@ -6,33 +6,34 @@ //----------------------------------------------------------------------------- #include "../solvespace.h" -void SShell::MergeCoincidentSurfaces(void) { +void SShell::MergeCoincidentSurfaces() { surface.ClearTags(); int i, j; SSurface *si, *sj; for(i = 0; i < surface.n; i++) { - si = &(surface.elem[i]); + si = &(surface[i]); if(si->tag) continue; // Let someone else clean up the empty surfaces; we can certainly merge // them, but we don't know how to calculate a reasonable bounding box. - if(si->trim.n == 0) continue; + if(si->trim.IsEmpty()) + continue; // And for now we handle only coincident planes, so no sense wasting // time on other surfaces. if(si->degm != 1 || si->degn != 1) continue; SEdgeList sel = {}; - si->MakeEdgesInto(this, &sel, SSurface::AS_XYZ); + si->MakeEdgesInto(this, &sel, SSurface::MakeAs::XYZ); bool mergedThisTime, merged = false; do { mergedThisTime = false; for(j = i + 1; j < surface.n; j++) { - sj = &(surface.elem[j]); + sj = &(surface[j]); if(sj->tag) continue; - if(!sj->CoincidentWith(si, true)) continue; + if(!sj->CoincidentWith(si, /*sameNormal=*/true)) continue; if(!sj->color.Equals(si->color)) continue; // But we do merge surfaces with different face entities, since // otherwise we'd hardly ever merge anything. @@ -42,7 +43,7 @@ void SShell::MergeCoincidentSurfaces(void) { // the bounding box tests less effective, and possibly things // less robust. SEdgeList tel = {}; - sj->MakeEdgesInto(this, &tel, SSurface::AS_XYZ); + sj->MakeEdgesInto(this, &tel, SSurface::MakeAs::XYZ); if(!sel.ContainsEdgeFrom(&tel)) { tel.Clear(); continue; @@ -52,15 +53,15 @@ void SShell::MergeCoincidentSurfaces(void) { sj->tag = 1; merged = true; mergedThisTime = true; - sj->MakeEdgesInto(this, &sel, SSurface::AS_XYZ); + sj->MakeEdgesInto(this, &sel, SSurface::MakeAs::XYZ); sj->trim.Clear(); // All the references to this surface get replaced with the // new srf SCurve *sc; for(sc = curve.First(); sc; sc = curve.NextAfter(sc)) { - if(sc->surfA.v == sj->h.v) sc->surfA = si->h; - if(sc->surfB.v == sj->h.v) sc->surfB = si->h; + if(sc->surfA == sj->h) sc->surfA = si->h; + if(sc->surfB == sj->h) sc->surfB = si->h; } } @@ -72,7 +73,7 @@ void SShell::MergeCoincidentSurfaces(void) { if(merged) { sel.CullExtraneousEdges(); si->trim.Clear(); - si->TrimFromEdgeList(&sel, false); + si->TrimFromEdgeList(&sel, /*asUv=*/false); // And we must choose control points such that all the trims lie // with u and v in [0, 1], so that the bbox tests work. diff --git a/src/srf/ratpoly.cpp b/src/srf/ratpoly.cpp index 2f6a0bb..46180a5 100644 --- a/src/srf/ratpoly.cpp +++ b/src/srf/ratpoly.cpp @@ -13,87 +13,30 @@ // and convergence should be fast by now. #define RATPOLY_EPS (LENGTH_EPS/(1e2)) -double SolveSpace::Bernstein(int k, int deg, double t) -{ - if(k > deg || k < 0) return 0; - - switch(deg) { - case 0: - return 1; - - case 1: - if(k == 0) { - return (1 - t); - } else if(k == 1) { - return t; - } - break; - - case 2: - if(k == 0) { - return (1 - t)*(1 - t); - } else if(k == 1) { - return 2*(1 - t)*t; - } else if(k == 2) { - return t*t; - } - break; - - case 3: - if(k == 0) { - return (1 - t)*(1 - t)*(1 - t); - } else if(k == 1) { - return 3*(1 - t)*(1 - t)*t; - } else if(k == 2) { - return 3*(1 - t)*t*t; - } else if(k == 3) { - return t*t*t; - } - break; - } - oops(); +static inline double Bernstein(int k, int deg, double t) { +// indexed by [degree][k][exponent] + static const double bernstein_coeff[4][4][4] = { + { { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 }, { 1.0,0.0,0.0,0.0 } }, + { { 1.0,-1.0,0.0,0.0 }, { 0.0,1.0,0.0,0.0 }, { 0.0,0.0,0.0,0.0 }, { 0.0,0.0,0.0,0.0 } }, + { { 1.0,-2.0,1.0,0.0 }, { 0.0,2.0,-2.0,0.0 },{ 0.0,0.0,1.0,0.0 }, { 0.0,0.0,0.0,0.0 } }, + { { 1.0,-3.0,3.0,-1.0 },{ 0.0,3.0,-6.0,3.0 },{ 0.0,0.0,3.0,-3.0}, { 0.0,0.0,0.0,1.0 } } }; + + const double *c = bernstein_coeff[deg][k]; + return (((c[3]*t+c[2])*t)+c[1])*t+c[0]; } -double SolveSpace::BernsteinDerivative(int k, int deg, double t) -{ - switch(deg) { - case 0: - return 0; - - case 1: - if(k == 0) { - return -1; - } else if(k == 1) { - return 1; - } - break; +static inline double BernsteinDerivative(int k, int deg, double t) { + static const double bernstein_derivative_coeff[4][4][3] = { + { { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } }, + { { -1.0,0.0,0.0 }, { 1.0,0.0,0.0 }, { 0.0,0.0,0.0 }, { 0.0,0.0,0.0 } }, + { { -2.0,2.0,0.0 }, { 2.0,-4.0,0.0 },{ 0.0,2.0,0.0 }, { 0.0,0.0,0.0 } }, + { { -3.0,6.0,-3.0 },{ 3.0,-12.0,9.0 },{ 0.0,6.0,-9.0}, { 0.0,0.0,3.0 } } }; - case 2: - if(k == 0) { - return -2 + 2*t; - } else if(k == 1) { - return 2 - 4*t; - } else if(k == 2) { - return 2*t; - } - break; - - case 3: - if(k == 0) { - return -3 + 6*t - 3*t*t; - } else if(k == 1) { - return 3 - 12*t + 9*t*t; - } else if(k == 2) { - return 6*t - 9*t*t; - } else if(k == 3) { - return 3*t*t; - } - break; - } - oops(); + const double *c = bernstein_derivative_coeff[deg][k]; + return ((c[2]*t)+c[1])*t+c[0]; } -Vector SBezier::PointAt(double t) { +Vector SBezier::PointAt(double t) const { Vector pt = Vector::From(0, 0, 0); double d = 0; @@ -107,7 +50,7 @@ Vector SBezier::PointAt(double t) { return pt; } -Vector SBezier::TangentAt(double t) { +Vector SBezier::TangentAt(double t) const { Vector pt = Vector::From(0, 0, 0), pt_p = Vector::From(0, 0, 0); double d = 0, d_p = 0; @@ -130,7 +73,7 @@ Vector SBezier::TangentAt(double t) { return ret; } -void SBezier::ClosestPointTo(Vector p, double *t, bool converge) { +void SBezier::ClosestPointTo(Vector p, double *t, bool mustConverge) const { int i; double minDist = VERY_POSITIVE; *t = 0; @@ -147,7 +90,7 @@ void SBezier::ClosestPointTo(Vector p, double *t, bool converge) { } Vector p0; - for(i = 0; i < (converge ? 15 : 5); i++) { + for(i = 0; i < (mustConverge ? 15 : 5); i++) { p0 = PointAt(*t); if(p0.Equals(p, RATPOLY_EPS)) { return; @@ -155,17 +98,17 @@ void SBezier::ClosestPointTo(Vector p, double *t, bool converge) { Vector dp = TangentAt(*t); Vector pc = p.ClosestPointOnLine(p0, dp); - *t += (pc.Minus(p0)).DivPivoting(dp); + *t += (pc.Minus(p0)).DivProjected(dp); } - if(converge) { + if(mustConverge) { dbp("didn't converge (closest point on bezier curve)"); } } -bool SBezier::PointOnThisAndCurve(SBezier *sbb, Vector *p) { +bool SBezier::PointOnThisAndCurve(const SBezier *sbb, Vector *p) const { double ta, tb; - this->ClosestPointTo(*p, &ta, false); - sbb ->ClosestPointTo(*p, &tb, false); + this->ClosestPointTo(*p, &ta, /*mustConverge=*/false); + sbb ->ClosestPointTo(*p, &tb, /*mustConverge=*/false); int i; for(i = 0; i < 20; i++) { @@ -187,7 +130,7 @@ bool SBezier::PointOnThisAndCurve(SBezier *sbb, Vector *p) { return false; } -void SBezier::SplitAt(double t, SBezier *bef, SBezier *aft) { +void SBezier::SplitAt(double t, SBezier *bef, SBezier *aft) const { Vector4 ct[4]; int i; for(i = 0; i <= deg; i++) { @@ -222,58 +165,68 @@ void SBezier::SplitAt(double t, SBezier *bef, SBezier *aft) { *aft = SBezier::From(cts, ct12_23, ct23, ct[3]); break; } - default: oops(); + default: ssassert(false, "Unexpected degree of spline"); } } -void SBezier::MakePwlInto(SEdgeList *sel, double chordTol) { +void SBezier::MakePwlInto(SEdgeList *sel, double chordTol, double max_dt) const { List lv = {}; - MakePwlInto(&lv, chordTol); + MakePwlInto(&lv, chordTol, max_dt); int i; for(i = 1; i < lv.n; i++) { - sel->AddEdge(lv.elem[i-1], lv.elem[i]); + sel->AddEdge(lv[i-1], lv[i]); } lv.Clear(); } -void SBezier::MakePwlInto(List *l, double chordTol) { +void SBezier::MakePwlInto(List *l, double chordTol, double max_dt) const { List lv = {}; - MakePwlInto(&lv, chordTol); + MakePwlInto(&lv, chordTol, max_dt); int i; for(i = 0; i < lv.n; i++) { SCurvePt scpt; scpt.tag = 0; - scpt.p = lv.elem[i]; + scpt.p = lv[i]; scpt.vertex = (i == 0) || (i == (lv.n - 1)); l->Add(&scpt); } lv.Clear(); } -void SBezier::MakePwlInto(SContour *sc, double chordTol) { +void SBezier::MakePwlInto(SContour *sc, double chordTol, double max_dt) const { List lv = {}; - MakePwlInto(&lv, chordTol); + MakePwlInto(&lv, chordTol, max_dt); int i; for(i = 0; i < lv.n; i++) { - sc->AddPoint(lv.elem[i]); + sc->AddPoint(lv[i]); } lv.Clear(); } -void SBezier::MakePwlInto(List *l, double chordTol) { +//-------------------------------------------------------------------------------------- +// all variants of MakePwlInto come here. Split a rational Bezier into Piecewise Linear +// segments that don't deviate from the actual curve by more than the chordTol distance. +// max_dt allows to force curves to be split into spans of no more than a certain +// length based on t-parameter. RemoveShortSegments() may delete points when dt <= 0.1 +//-------------------------------------------------------------------------------------- +void SBezier::MakePwlInto(List *l, double chordTol, double max_dt) const { if(EXACT(chordTol == 0)) { // Use the default chord tolerance. chordTol = SS.ChordTolMm(); } + // Never do fewer than three intermediate points for curves; people seem to get + // unhappy when their circles turn into squares, but maybe less + // unhappy with octagons. Now 16-gons. + if (EXACT(max_dt == 0.0)) { + max_dt = (deg == 1) ? 1.0 : 0.25; + } l->Add(&(ctrl[0])); - if(deg == 1) { + // don't split first degee (lines) unless asked to by the caller via max_dt + if((deg == 1) && (max_dt >= 1.0)) { l->Add(&(ctrl[1])); } else { - // Never do fewer than one intermediate point; people seem to get - // unhappy when their circles turn into squares, but maybe less - // unhappy with octagons. - MakePwlInitialWorker(l, 0.0, 0.5, chordTol); - MakePwlInitialWorker(l, 0.5, 1.0, chordTol); + MakePwlInitialWorker(l, 0.0, 0.5, chordTol, max_dt); + MakePwlInitialWorker(l, 0.5, 1.0, chordTol, max_dt); } } -void SBezier::MakePwlWorker(List *l, double ta, double tb, double chordTol) +void SBezier::MakePwlWorker(List *l, double ta, double tb, double chordTol, double max_dt) const { Vector pa = PointAt(ta); Vector pb = PointAt(tb); @@ -282,16 +235,16 @@ void SBezier::MakePwlWorker(List *l, double ta, double tb, double chordT double d = pm.DistanceToLine(pa, pb.Minus(pa)); double step = 1.0/SS.GetMaxSegments(); - if((tb - ta) < step || d < chordTol) { + if(((tb - ta) < step || d < chordTol) && ((tb-ta) <= max_dt) ) { // A previous call has already added the beginning of our interval. l->Add(&pb); } else { double tm = (ta + tb) / 2; - MakePwlWorker(l, ta, tm, chordTol); - MakePwlWorker(l, tm, tb, chordTol); + MakePwlWorker(l, ta, tm, chordTol, max_dt); + MakePwlWorker(l, tm, tb, chordTol, max_dt); } } -void SBezier::MakePwlInitialWorker(List *l, double ta, double tb, double chordTol) +void SBezier::MakePwlInitialWorker(List *l, double ta, double tb, double chordTol, double max_dt) const { Vector pa = PointAt(ta); Vector pb = PointAt(tb); @@ -308,24 +261,56 @@ void SBezier::MakePwlInitialWorker(List *l, double ta, double tb, double double d = max({ pm1.DistanceToLine(pa, dir), pm2.DistanceToLine(pa, dir), - pm3.DistanceToLine(pa, dir) + pm3.DistanceToLine(pa, dir) }); double step = 1.0/SS.GetMaxSegments(); - if((tb - ta) < step || d < chordTol) { + if( ((tb - ta) < step || d < chordTol) && ((tb-ta) <= max_dt) ) { // A previous call has already added the beginning of our interval. l->Add(&pb); } else { double tm = (ta + tb) / 2; - MakePwlWorker(l, ta, tm, chordTol); - MakePwlWorker(l, tm, tb, chordTol); + MakePwlWorker(l, ta, tm, chordTol, max_dt); + MakePwlWorker(l, tm, tb, chordTol, max_dt); + } +} + +void SBezier::MakeNonrationalCubicInto(SBezierList *bl, double tolerance, int depth) const { + Vector t0 = TangentAt(0), t1 = TangentAt(1); + // The curve is correct, and the first derivatives are correct, at the + // endpoints. + SBezier bnr = SBezier::From( + Start(), + Start().Plus(t0.ScaledBy(1.0/3)), + Finish().Minus(t1.ScaledBy(1.0/3)), + Finish()); + + bool closeEnough = true; + int i; + for(i = 1; i <= 3; i++) { + double t = i/4.0; + Vector p0 = PointAt(t), + pn = bnr.PointAt(t); + double d = (p0.Minus(pn)).Magnitude(); + if(d > tolerance) { + closeEnough = false; + } + } + + if(closeEnough || depth > 3) { + bl->l.Add(this); + } else { + SBezier bef, aft; + SplitAt(0.5, &bef, &aft); + bef.MakeNonrationalCubicInto(bl, tolerance, depth+1); + aft.MakeNonrationalCubicInto(bl, tolerance, depth+1); } } -Vector SSurface::PointAt(Point2d puv) { +Vector SSurface::PointAt(Point2d puv) const { return PointAt(puv.x, puv.y); } -Vector SSurface::PointAt(double u, double v) { +Vector SSurface::PointAt(double u, double v) const { Vector num = Vector::From(0, 0, 0); double den = 0; @@ -343,7 +328,7 @@ Vector SSurface::PointAt(double u, double v) { return num; } -void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv) { +void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv, bool retry) const { Vector num = Vector::From(0, 0, 0), num_u = Vector::From(0, 0, 0), num_v = Vector::From(0, 0, 0); @@ -375,23 +360,31 @@ void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv) { *tv = ((num_v.ScaledBy(den)).Minus(num.ScaledBy(den_v))); *tv = tv->ScaledBy(1.0/(den*den)); + + // Tangent is zero at sungularities like the north pole. Move away a bit and retry. + if(tv->Equals(Vector::From(0,0,0)) && retry) + TangentsAt(u+(0.5-u)*0.00001, v, tu, tv, false); + if(tu->Equals(Vector::From(0,0,0)) && retry) + TangentsAt(u, v+(0.5-v)*0.00001, tu, tv, false); } -Vector SSurface::NormalAt(Point2d puv) { +Vector SSurface::NormalAt(Point2d puv) const { return NormalAt(puv.x, puv.y); } -Vector SSurface::NormalAt(double u, double v) { + +Vector SSurface::NormalAt(double u, double v) const { Vector tu, tv; TangentsAt(u, v, &tu, &tv); return tu.Cross(tv); } -void SSurface::ClosestPointTo(Vector p, Point2d *puv, bool converge) { - ClosestPointTo(p, &(puv->x), &(puv->y), converge); +void SSurface::ClosestPointTo(Vector p, Point2d *puv, bool mustConverge) { + ClosestPointTo(p, &(puv->x), &(puv->y), mustConverge); } -void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool converge) { + +void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool mustConverge) { // A few special cases first; when control points are coincident the - // derivative goes to zero at the conrol points, and would result in + // derivative goes to zero at the control points, and would result in // nonconvergence. We avoid that here, and also guarantee a consistent // (u, v) (of the infinitely many possible in one parameter). if(p.Equals(ctrl[0] [0] )) { *u = 0; *v = 0; return; } @@ -405,9 +398,14 @@ void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool converge) { bu = (ctrl[1][0]).Minus(orig), bv = (ctrl[0][1]).Minus(orig); if((ctrl[1][1]).Equals(orig.Plus(bu).Plus(bv))) { + + Vector n = bu.Cross(bv); + Vector ty = n.Cross(bu).ScaledBy(1.0/bu.MagSquared()); + Vector tx = bv.Cross(n).ScaledBy(1.0/bv.MagSquared()); + Vector dp = p.Minus(orig); - *u = dp.Dot(bu) / bu.MagSquared(); - *v = dp.Dot(bv) / bv.MagSquared(); + *u = dp.Dot(bu) / tx.MagSquared(); + *v = dp.Dot(bv) / ty.MagSquared(); return; } } @@ -416,9 +414,9 @@ void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool converge) { // good if we're working our way along a curve or something else where // we project successive points that are close to each other; something // like a 20% speedup empirically. - if(converge) { + if(mustConverge) { double ut = cached.x, vt = cached.y; - if(ClosestPointNewton(p, &ut, &vt, converge)) { + if(ClosestPointNewton(p, &ut, &vt, mustConverge)) { cached.x = *u = ut; cached.y = *v = vt; return; @@ -443,55 +441,70 @@ void SSurface::ClosestPointTo(Vector p, double *u, double *v, bool converge) { } } - if(ClosestPointNewton(p, u, v, converge)) { + if(ClosestPointNewton(p, u, v, mustConverge)) { cached.x = *u; cached.y = *v; return; } // If we failed to converge, then at least don't return NaN. - if(isnan(*u) || isnan(*v)) { + if(mustConverge) { +// This is expected not to converge when the target point is not on the surface but nearby. +// let's not pollute the output window for normal use. +// Vector p0 = PointAt(*u, *v); +// dbp("didn't converge"); +// dbp("have %.3f %.3f %.3f", CO(p0)); +// dbp("want %.3f %.3f %.3f", CO(p)); +// dbp("distance = %g", (p.Minus(p0)).Magnitude()); + } + if(IsReasonable(*u) || IsReasonable(*v)) { *u = *v = 0; } } -bool SSurface::ClosestPointNewton(Vector p, double *u, double *v, bool converge) +bool SSurface::ClosestPointNewton(Vector p, double *u, double *v, bool mustConverge) const { // Initial guess is in u, v; refine by Newton iteration. Vector p0 = Vector::From(0, 0, 0); - for(int i = 0; i < (converge ? 25 : 5); i++) { + for(int i = 0; i < (mustConverge ? 25 : 5); i++) { p0 = PointAt(*u, *v); - if(converge) { + if(mustConverge) { if(p0.Equals(p, RATPOLY_EPS)) { return true; } } - Vector tu, tv; + Vector tu, tv, tx, ty; TangentsAt(*u, *v, &tu, &tv); + Vector n = tu.Cross(tv); + // since tu and tv may not be orthogonal, use y in place of v. + // |y| = |v|sin(theta) where theta is the angle between tu and tv. + ty = n.Cross(tu).ScaledBy(1.0/tu.MagSquared()); + tx = tv.Cross(n).ScaledBy(1.0/tv.MagSquared()); // Project the point into a plane through p0, with basis tu, tv; a // second-order thing would converge faster but needs second // derivatives. Vector dp = p.Minus(p0); - double du = dp.Dot(tu), dv = dp.Dot(tv); - *u += du / (tu.MagSquared()); - *v += dv / (tv.MagSquared()); - } + double du = dp.Dot(tx), + dv = dp.Dot(ty); + *u += du / (tx.MagSquared()); + *v += dv / (ty.MagSquared()); + + if (*u < 0.0) *u = 0.0; + else if (*u > 1.0) *u = 1.0; + if (*v < 0.0) *v = 0.0; + else if (*v > 1.0) *v = 1.0; - if(converge) { - dbp("didn't converge"); - dbp("have %.3f %.3f %.3f", CO(p0)); - dbp("want %.3f %.3f %.3f", CO(p)); - dbp("distance = %g", (p.Minus(p0)).Magnitude()); } + return false; } -bool SSurface::PointIntersectingLine(Vector p0, Vector p1, double *u, double *v) +bool SSurface::PointIntersectingLine(Vector p0, Vector p1, double *u, double *v) const { int i; - for(i = 0; i < 15; i++) { + for(i = 0; i < 20; i++) { Vector pi, p, tu, tv; p = PointAt(*u, *v); TangentsAt(*u, *v, &tu, &tv); @@ -501,18 +514,25 @@ bool SSurface::PointIntersectingLine(Vector p0, Vector p1, double *u, double *v) bool parallel; pi = Vector::AtIntersectionOfPlaneAndLine(n, d, p0, p1, ¶llel); - if(parallel) break; + if(parallel) { + dbp("parallel (surface intersecting line)"); + break; + } // Check for convergence if(pi.Equals(p, RATPOLY_EPS)) return true; + n = tu.Cross(tv); + Vector ty = n.Cross(tu).ScaledBy(1.0/tu.MagSquared()); + Vector tx = tv.Cross(n).ScaledBy(1.0/tv.MagSquared()); + // Adjust our guess and iterate Vector dp = pi.Minus(p); - double du = dp.Dot(tu), dv = dp.Dot(tv); - *u += du / (tu.MagSquared()); - *v += dv / (tv.MagSquared()); + double du = dp.Dot(tx), dv = dp.Dot(ty); + *u += du / tx.MagSquared(); + *v += dv / ty.MagSquared(); } -// dbp("didn't converge (surface intersecting line)"); + dbp("didn't converge (surface intersecting line)"); return false; } @@ -523,7 +543,7 @@ Vector SSurface::ClosestPointOnThisAndSurface(SSurface *srf2, Vector p) { SSurface *srf[2] = { this, srf2 }; for(j = 0; j < 2; j++) { - (srf[j])->ClosestPointTo(p, &(puv[j]), false); + (srf[j])->ClosestPointTo(p, &(puv[j]), /*mustConverge=*/false); } for(i = 0; i < 10; i++) { @@ -548,10 +568,14 @@ Vector SSurface::ClosestPointOnThisAndSurface(SSurface *srf2, Vector p) { // Adjust our guess and iterate for(j = 0; j < 2; j++) { + Vector n = tu[j].Cross(tv[j]); + Vector ty = n.Cross(tu[j]).ScaledBy(1.0/tu[j].MagSquared()); + Vector tx = tv[j].Cross(n).ScaledBy(1.0/tv[j].MagSquared()); + Vector dc = pc.Minus(cp[j]); - double du = dc.Dot(tu[j]), dv = dc.Dot(tv[j]); - puv[j].x += du / ((tu[j]).MagSquared()); - puv[j].y += dv / ((tv[j]).MagSquared()); + double du = dc.Dot(tx), dv = dc.Dot(ty); + puv[j].x += du / tx.MagSquared(); + puv[j].y += dv / ty.MagSquared(); } } if(i >= 10) { @@ -564,16 +588,15 @@ Vector SSurface::ClosestPointOnThisAndSurface(SSurface *srf2, Vector p) { ((srf[1])->PointAt(puv[1]))).ScaledBy(0.5); } -void SSurface::PointOnSurfaces(SSurface *s1, SSurface *s2, - double *up, double *vp) +void SSurface::PointOnSurfaces(SSurface *s1, SSurface *s2, double *up, double *vp) { double u[3] = { *up, 0, 0 }, v[3] = { *vp, 0, 0 }; SSurface *srf[3] = { this, s1, s2 }; // Get initial guesses for (u, v) in the other surfaces Vector p = PointAt(*u, *v); - (srf[1])->ClosestPointTo(p, &(u[1]), &(v[1]), false); - (srf[2])->ClosestPointTo(p, &(u[2]), &(v[2]), false); + (srf[1])->ClosestPointTo(p, &(u[1]), &(v[1]), /*mustConverge=*/false); + (srf[2])->ClosestPointTo(p, &(u[2]), &(v[2]), /*mustConverge=*/false); int i, j; for(i = 0; i < 20; i++) { @@ -601,15 +624,78 @@ void SSurface::PointOnSurfaces(SSurface *s1, SSurface *s2, Vector pi = Vector::AtIntersectionOfPlanes(n[0], d[0], n[1], d[1], n[2], d[2], ¶llel); - if(parallel) break; + + if(parallel) { // lets try something else for parallel planes + pi = p[0].Plus(p[1]).Plus(p[2]).ScaledBy(1.0/3.0); + } for(j = 0; j < 3; j++) { + Vector n = tu[j].Cross(tv[j]); + Vector ty = n.Cross(tu[j]).ScaledBy(1.0/tu[j].MagSquared()); + Vector tx = tv[j].Cross(n).ScaledBy(1.0/tv[j].MagSquared()); + Vector dp = pi.Minus(p[j]); - double du = dp.Dot(tu[j]), dv = dp.Dot(tv[j]); - u[j] += du / (tu[j]).MagSquared(); - v[j] += dv / (tv[j]).MagSquared(); + double du = dp.Dot(tx), dv = dp.Dot(ty); + + u[j] += du / tx.MagSquared(); + v[j] += dv / ty.MagSquared(); } } dbp("didn't converge (three surfaces intersecting)"); } +void SSurface::PointOnCurve(const SBezier *curve, double *up, double *vp) +{ + Vector tu,tv,n; + double u = *up, v = *vp; + Vector ps = PointAt(u, v); + // Get initial guesses for t on the curve + double tCurve = 0.5; + curve->ClosestPointTo(ps, &tCurve, /*mustConverge=*/false); + if(tCurve < 0.0) tCurve = 0.0; + if(tCurve > 1.0) tCurve = 1.0; + + for(int i = 0; i < 30; i++) { + // Approximate the surface by a plane + Vector ps = PointAt(u, v); + TangentsAt(u, v, &tu, &tv); + n = tu.Cross(tv).WithMagnitude(1); + + // point on curve and tangent line direction + Vector pc = curve->PointAt(tCurve); + Vector tc = curve->TangentAt(tCurve); + + if(ps.Equals(pc, RATPOLY_EPS)) { + *up = u; + *vp = v; + return; + } + + //pi is where the curve tangent line intersects the surface tangent plane + Vector pi; + double d = tc.Dot(n); + if (fabs(d) < 1e-10) { // parallel line and plane, guess the average rather than fail + pi = pc.Plus(ps).ScaledBy(0.5); + } else { + pi = pc.Minus(tc.ScaledBy(pc.Minus(ps).Dot(n)/d)); + } + + // project the point onto the tangent plane and line + { + Vector n = tu.Cross(tv); + Vector ty = n.Cross(tu).ScaledBy(1.0/tu.MagSquared()); + Vector tx = tv.Cross(n).ScaledBy(1.0/tv.MagSquared()); + + Vector dp = pi.Minus(ps); + double du = dp.Dot(tx), dv = dp.Dot(ty); + + u += du / tx.MagSquared(); + v += dv / ty.MagSquared(); + } + tCurve += pi.Minus(pc).Dot(tc) / tc.MagSquared(); + if(tCurve < 0.0) tCurve = 0.0; + if(tCurve > 1.0) tCurve = 1.0; + } + dbp("didn't converge (surface and curve intersecting)"); +} + diff --git a/src/srf/raycast.cpp b/src/srf/raycast.cpp index 808287f..5877928 100644 --- a/src/srf/raycast.cpp +++ b/src/srf/raycast.cpp @@ -16,7 +16,7 @@ const double SShell::DOTP_TOL = 1e-5; extern int FLAG; -double SSurface::DepartureFromCoplanar(void) { +double SSurface::DepartureFromCoplanar() const { int i, j; int ia, ja, ib = 0, jb = 0, ic = 0, jc = 0; double best; @@ -73,7 +73,7 @@ double SSurface::DepartureFromCoplanar(void) { return farthest; } -void SSurface::WeightControlPoints(void) { +void SSurface::WeightControlPoints() { int i, j; for(i = 0; i <= degm; i++) { for(j = 0; j <= degn; j++) { @@ -81,7 +81,7 @@ void SSurface::WeightControlPoints(void) { } } } -void SSurface::UnWeightControlPoints(void) { +void SSurface::UnWeightControlPoints() { int i, j; for(i = 0; i <= degm; i++) { for(j = 0; j <= degn; j++) { @@ -130,39 +130,38 @@ void SSurface::SplitInHalf(bool byU, SSurface *sa, SSurface *sb) { sa->degn = sb->degn = degn; // by de Casteljau's algorithm in a projective space; so we must work - // on points (w*x, w*y, w*z, w) - WeightControlPoints(); + // on points (w*x, w*y, w*z, w) so create a temporary copy + SSurface st; + st = *this; + st.WeightControlPoints(); switch(byU ? degm : degn) { case 1: - sa->CopyRowOrCol (byU, 0, this, 0); - sb->CopyRowOrCol (byU, 1, this, 1); + sa->CopyRowOrCol (byU, 0, &st, 0); + sb->CopyRowOrCol (byU, 1, &st, 1); - sa->BlendRowOrCol(byU, 1, this, 0, this, 1); - sb->BlendRowOrCol(byU, 0, this, 0, this, 1); + sa->BlendRowOrCol(byU, 1, &st, 0, &st, 1); + sb->BlendRowOrCol(byU, 0, &st, 0, &st, 1); break; case 2: - sa->CopyRowOrCol (byU, 0, this, 0); - sb->CopyRowOrCol (byU, 2, this, 2); + sa->CopyRowOrCol (byU, 0, &st, 0); + sb->CopyRowOrCol (byU, 2, &st, 2); - sa->BlendRowOrCol(byU, 1, this, 0, this, 1); - sb->BlendRowOrCol(byU, 1, this, 1, this, 2); + sa->BlendRowOrCol(byU, 1, &st, 0, &st, 1); + sb->BlendRowOrCol(byU, 1, &st, 1, &st, 2); sa->BlendRowOrCol(byU, 2, sa, 1, sb, 1); sb->BlendRowOrCol(byU, 0, sa, 1, sb, 1); break; case 3: { - SSurface st; - st.degm = degm; st.degn = degn; + sa->CopyRowOrCol (byU, 0, &st, 0); + sb->CopyRowOrCol (byU, 3, &st, 3); - sa->CopyRowOrCol (byU, 0, this, 0); - sb->CopyRowOrCol (byU, 3, this, 3); - - sa->BlendRowOrCol(byU, 1, this, 0, this, 1); - sb->BlendRowOrCol(byU, 2, this, 2, this, 3); - st. BlendRowOrCol(byU, 0, this, 1, this, 2); // scratch var + sa->BlendRowOrCol(byU, 1, &st, 0, &st, 1); + sb->BlendRowOrCol(byU, 2, &st, 2, &st, 3); + st. BlendRowOrCol(byU, 0, &st, 1, &st, 2); // use row/col 0 as scratch sa->BlendRowOrCol(byU, 2, sa, 1, &st, 0); sb->BlendRowOrCol(byU, 1, sb, 2, &st, 0); @@ -172,12 +171,11 @@ void SSurface::SplitInHalf(bool byU, SSurface *sa, SSurface *sb) { break; } - default: oops(); + default: ssassert(false, "Unexpected degree of spline"); } sa->UnWeightControlPoints(); sb->UnWeightControlPoints(); - UnWeightControlPoints(); } //----------------------------------------------------------------------------- @@ -190,12 +188,12 @@ void SSurface::SplitInHalf(bool byU, SSurface *sa, SSurface *sb) { //----------------------------------------------------------------------------- void SSurface::AllPointsIntersectingUntrimmed(Vector a, Vector b, int *cnt, int *level, - List *l, bool segment, + List *l, bool asSegment, SSurface *sorig) { // Test if the line intersects our axis-aligned bounding box; if no, then // no possibility of an intersection - if(LineEntirelyOutsideBbox(a, b, segment)) return; + if(LineEntirelyOutsideBbox(a, b, asSegment)) return; if(*cnt > 2000) { dbp("!!! too many subdivisions (level=%d)!", *level); @@ -212,7 +210,7 @@ void SSurface::AllPointsIntersectingUntrimmed(Vector a, Vector b, ctrl[degm][0 ]).Plus( ctrl[degm][degn]).ScaledBy(0.25); Inter inter; - sorig->ClosestPointTo(p, &(inter.p.x), &(inter.p.y), false); + sorig->ClosestPointTo(p, &(inter.p.x), &(inter.p.y), /*mustConverge=*/false); if(sorig->PointIntersectingLine(a, b, &(inter.p.x), &(inter.p.y))) { Vector p = sorig->PointAt(inter.p.x, inter.p.y); // Debug check, verify that the point lies in both surfaces @@ -232,9 +230,9 @@ void SSurface::AllPointsIntersectingUntrimmed(Vector a, Vector b, int nextLevel = (*level) + 1; (*level) = nextLevel; - surf0.AllPointsIntersectingUntrimmed(a, b, cnt, level, l, segment, sorig); + surf0.AllPointsIntersectingUntrimmed(a, b, cnt, level, l, asSegment, sorig); (*level) = nextLevel; - surf1.AllPointsIntersectingUntrimmed(a, b, cnt, level, l, segment, sorig); + surf1.AllPointsIntersectingUntrimmed(a, b, cnt, level, l, asSegment, sorig); } //----------------------------------------------------------------------------- @@ -247,9 +245,9 @@ void SSurface::AllPointsIntersectingUntrimmed(Vector a, Vector b, //----------------------------------------------------------------------------- void SSurface::AllPointsIntersecting(Vector a, Vector b, List *l, - bool seg, bool trimmed, bool inclTangent) + bool asSegment, bool trimmed, bool inclTangent) { - if(LineEntirelyOutsideBbox(a, b, seg)) return; + if(LineEntirelyOutsideBbox(a, b, asSegment)) return; Vector ba = b.Minus(a); double bam = ba.Magnitude(); @@ -266,7 +264,7 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b, double d = n.Dot(PointAt(0, 0)); // Trim to line segment now if requested, don't generate points that // would just get discarded later. - if(!seg || + if(!asSegment || (n.Dot(a) > d + LENGTH_EPS && n.Dot(b) < d - LENGTH_EPS) || (n.Dot(b) > d + LENGTH_EPS && n.Dot(a) < d - LENGTH_EPS)) { @@ -311,7 +309,7 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b, } int i; for(i = 0; i < ip_n; i++) { - double t = (ip[i].Minus(ap)).DivPivoting(bp.Minus(ap)); + double t = (ip[i].Minus(ap)).DivProjected(bp.Minus(ap)); // This is a point on the circle; but is it on the arc? Point2d pp = ap.Plus((bp.Minus(ap)).ScaledBy(t)); double theta = atan2(pp.y, pp.x); @@ -334,7 +332,7 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b, } else { // General numerical solution by subdivision, fallback int cnt = 0, level = 0; - AllPointsIntersectingUntrimmed(a, b, &cnt, &level, &inters, seg, this); + AllPointsIntersectingUntrimmed(a, b, &cnt, &level, &inters, asSegment, this); } // Remove duplicate intersection points @@ -342,27 +340,27 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b, int i, j; for(i = 0; i < inters.n; i++) { for(j = i + 1; j < inters.n; j++) { - if(inters.elem[i].p.Equals(inters.elem[j].p)) { - inters.elem[j].tag = 1; + if(inters[i].p.Equals(inters[j].p)) { + inters[j].tag = 1; } } } inters.RemoveTagged(); for(i = 0; i < inters.n; i++) { - Point2d puv = inters.elem[i].p; + Point2d puv = inters[i].p; // Make sure the point lies within the finite line segment Vector pxyz = PointAt(puv.x, puv.y); - double t = (pxyz.Minus(a)).DivPivoting(ba); - if(seg && (t > 1 - LENGTH_EPS/bam || t < LENGTH_EPS/bam)) { + double t = (pxyz.Minus(a)).DivProjected(ba); + if(asSegment && (t > 1 - LENGTH_EPS/bam || t < LENGTH_EPS/bam)) { continue; } // And that it lies inside our trim region Point2d dummy = { 0, 0 }; - int c = (bsp) ? bsp->ClassifyPoint(puv, dummy, this) : SBspUv::OUTSIDE; - if(trimmed && c == SBspUv::OUTSIDE) { + SBspUv::Class c = (bsp) ? bsp->ClassifyPoint(puv, dummy, this) : SBspUv::Class::OUTSIDE; + if(trimmed && c == SBspUv::Class::OUTSIDE) { continue; } @@ -372,7 +370,7 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b, si.surfNormal = NormalAt(puv.x, puv.y); si.pinter = puv; si.srf = this; - si.onEdge = (c != SBspUv::INSIDE); + si.onEdge = (c != SBspUv::Class::INSIDE); l->Add(&si); } @@ -381,18 +379,19 @@ void SSurface::AllPointsIntersecting(Vector a, Vector b, void SShell::AllPointsIntersecting(Vector a, Vector b, List *il, - bool seg, bool trimmed, bool inclTangent) + bool asSegment, bool trimmed, bool inclTangent) { SSurface *ss; for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { - ss->AllPointsIntersecting(a, b, il, seg, trimmed, inclTangent); + ss->AllPointsIntersecting(a, b, il, + asSegment, trimmed, inclTangent); } } -int SShell::ClassifyRegion(Vector edge_n, Vector inter_surf_n, - Vector edge_surf_n) +SShell::Class SShell::ClassifyRegion(Vector edge_n, Vector inter_surf_n, + Vector edge_surf_n) const { double dot = inter_surf_n.DirectionCosineWith(edge_n); if(fabs(dot) < DOTP_TOL) { @@ -400,14 +399,14 @@ int SShell::ClassifyRegion(Vector edge_n, Vector inter_surf_n, // are coincident. Test the edge's surface normal // to see if it's with same or opposite normals. if(inter_surf_n.Dot(edge_surf_n) > 0) { - return COINC_SAME; + return Class::COINC_SAME; } else { - return COINC_OPP; + return Class::COINC_OPP; } } else if(dot > 0) { - return OUTSIDE; + return Class::OUTSIDE; } else { - return INSIDE; + return Class::INSIDE; } } @@ -420,21 +419,24 @@ int SShell::ClassifyRegion(Vector edge_n, Vector inter_surf_n, // using the closest intersection point. If the ray hits a surface on edge, // then just reattempt in a different random direction. //----------------------------------------------------------------------------- -bool SShell::ClassifyEdge(int *indir, int *outdir, + +// table of vectors in 6 arbitrary directions covering 4 of the 8 octants. +// use overlapping sets of 3 to reduce memory usage. +static const double Random[8] = {1.278, 5.0103, 9.427, -2.331, 7.13, 2.954, 5.034, -4.777}; + +bool SShell::ClassifyEdge(Class *indir, Class *outdir, Vector ea, Vector eb, Vector p, Vector edge_n_in, Vector edge_n_out, Vector surf_n) { List l = {}; - srand(0); - // First, check for edge-on-edge int edge_inters = 0; Vector inter_surf_n[2], inter_edge_n[2]; SSurface *srf; for(srf = surface.First(); srf; srf = surface.NextAfter(srf)) { - if(srf->LineEntirelyOutsideBbox(ea, eb, true)) continue; + if(srf->LineEntirelyOutsideBbox(ea, eb, /*asSegment=*/true)) continue; SEdgeList *sel = &(srf->edges); SEdge *se; @@ -446,7 +448,7 @@ bool SShell::ClassifyEdge(int *indir, int *outdir, if(edge_inters < 2) { // Edge-on-edge case Point2d pm; - srf->ClosestPointTo(p, &pm, false); + srf->ClosestPointTo(p, &pm, /*mustConverge=*/false); // A vector normal to the surface, at the intersection point inter_surf_n[edge_inters] = srf->NormalAt(pm); // A vector normal to the intersecting edge (but within the @@ -462,7 +464,7 @@ bool SShell::ClassifyEdge(int *indir, int *outdir, } if(edge_inters == 2) { - // TODO, make this use the appropriate curved normals + //! @todo make this use the appropriate curved normals double dotp[2]; for(int i = 0; i < 2; i++) { dotp[i] = edge_n_out.DirectionCosineWith(inter_surf_n[i]); @@ -474,7 +476,7 @@ bool SShell::ClassifyEdge(int *indir, int *outdir, swap(inter_edge_n[0], inter_edge_n[1]); } - int coinc = (surf_n.Dot(inter_surf_n[0])) > 0 ? COINC_SAME : COINC_OPP; + Class coinc = (surf_n.Dot(inter_surf_n[0])) > 0 ? Class::COINC_SAME : Class::COINC_OPP; if(fabs(dotp[0]) < DOTP_TOL && fabs(dotp[1]) < DOTP_TOL) { // This is actually an edge on face case, just that the face @@ -484,25 +486,25 @@ bool SShell::ClassifyEdge(int *indir, int *outdir, } else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] > DOTP_TOL) { if(edge_n_out.Dot(inter_edge_n[0]) > 0) { *indir = coinc; - *outdir = OUTSIDE; + *outdir = Class::OUTSIDE; } else { - *indir = INSIDE; + *indir = Class::INSIDE; *outdir = coinc; } } else if(fabs(dotp[0]) < DOTP_TOL && dotp[1] < -DOTP_TOL) { if(edge_n_out.Dot(inter_edge_n[0]) > 0) { *indir = coinc; - *outdir = INSIDE; + *outdir = Class::INSIDE; } else { - *indir = OUTSIDE; + *indir = Class::OUTSIDE; *outdir = coinc; } } else if(dotp[0] > DOTP_TOL && dotp[1] > DOTP_TOL) { - *indir = INSIDE; - *outdir = OUTSIDE; + *indir = Class::INSIDE; + *outdir = Class::OUTSIDE; } else if(dotp[0] < -DOTP_TOL && dotp[1] < -DOTP_TOL) { - *indir = OUTSIDE; - *outdir = INSIDE; + *indir = Class::OUTSIDE; + *outdir = Class::INSIDE; } else { // Edge is tangent to the shell at shell's edge, so can't be // a boundary of the surface. @@ -519,21 +521,21 @@ bool SShell::ClassifyEdge(int *indir, int *outdir, // the additional error from the line intersection. for(srf = surface.First(); srf; srf = surface.NextAfter(srf)) { - if(srf->LineEntirelyOutsideBbox(ea, eb, true)) continue; + if(srf->LineEntirelyOutsideBbox(ea, eb, /*asSegment=*/true)) continue; Point2d puv; - srf->ClosestPointTo(p, &(puv.x), &(puv.y), false); + srf->ClosestPointTo(p, &(puv.x), &(puv.y), /*mustConverge=*/false); Vector pp = srf->PointAt(puv); if((pp.Minus(p)).Magnitude() > LENGTH_EPS) continue; Point2d dummy = { 0, 0 }; - int c = (srf->bsp) ? srf->bsp->ClassifyPoint(puv, dummy, srf) : SBspUv::OUTSIDE; - if(c == SBspUv::OUTSIDE) continue; + SBspUv::Class c = (srf->bsp) ? srf->bsp->ClassifyPoint(puv, dummy, srf) : SBspUv::Class::OUTSIDE; + if(c == SBspUv::Class::OUTSIDE) continue; // Edge-on-face (unless edge-on-edge above superceded) Point2d pin, pout; - srf->ClosestPointTo(p.Plus(edge_n_in), &pin, false); - srf->ClosestPointTo(p.Plus(edge_n_out), &pout, false); + srf->ClosestPointTo(p.Plus(edge_n_in), &pin, /*mustConverge=*/false); + srf->ClosestPointTo(p.Plus(edge_n_out), &pout, /*mustConverge=*/false); Vector surf_n_in = srf->NormalAt(pin), surf_n_out = srf->NormalAt(pout); @@ -550,21 +552,22 @@ bool SShell::ClassifyEdge(int *indir, int *outdir, // Cast a ray in a random direction (two-sided so that we test if // the point lies on a surface, but use only one side for in/out // testing) - Vector ray = Vector::From(Random(1), Random(1), Random(1)); + Vector ray = Vector::From(Random[cnt], Random[cnt+1], Random[cnt+2]); AllPointsIntersecting( - p.Minus(ray), p.Plus(ray), &l, false, true, false); + p.Minus(ray), p.Plus(ray), &l, + /*asSegment=*/false, /*trimmed=*/true, /*inclTangent=*/false); // no intersections means it's outside - *indir = OUTSIDE; - *outdir = OUTSIDE; + *indir = Class::OUTSIDE; + *outdir = Class::OUTSIDE; double dmin = VERY_POSITIVE; bool onEdge = false; edge_inters = 0; SInter *si; for(si = l.First(); si; si = l.NextAfter(si)) { - double t = ((si->p).Minus(p)).DivPivoting(ray); + double t = ((si->p).Minus(p)).DivProjected(ray); if(t*ray.Magnitude() < -LENGTH_EPS) { // wrong side, doesn't count continue; @@ -583,11 +586,11 @@ bool SShell::ClassifyEdge(int *indir, int *outdir, // Edge does not lie on surface; either strictly inside // or strictly outside if((si->surfNormal).Dot(ray) > 0) { - *indir = INSIDE; - *outdir = INSIDE; + *indir = Class::INSIDE; + *outdir = Class::INSIDE; } else { - *indir = OUTSIDE; - *outdir = OUTSIDE; + *indir = Class::OUTSIDE; + *outdir = Class::OUTSIDE; } onEdge = si->onEdge; } @@ -598,7 +601,8 @@ bool SShell::ClassifyEdge(int *indir, int *outdir, // then our ray always lies on edge, and that's okay. Otherwise // try again in a different random direction. if(!onEdge) break; - if(cnt++ > 5) { + cnt++; + if(cnt > 5) { dbp("can't find a ray that doesn't hit on edge!"); dbp("on edge = %d, edge_inters = %d", onEdge, edge_inters); SS.nakedEdges.AddEdge(ea, eb); diff --git a/src/srf/surface.cpp b/src/srf/surface.cpp index 3f3178c..815aeda 100644 --- a/src/srf/surface.cpp +++ b/src/srf/surface.cpp @@ -24,7 +24,7 @@ SSurface SSurface::FromExtrusionOf(SBezier *sb, Vector t0, Vector t1) { return ret; } -bool SSurface::IsExtrusion(SBezier *of, Vector *alongp) { +bool SSurface::IsExtrusion(SBezier *of, Vector *alongp) const { int i; if(degn != 1) return false; @@ -52,7 +52,7 @@ bool SSurface::IsExtrusion(SBezier *of, Vector *alongp) { } bool SSurface::IsCylinder(Vector *axis, Vector *center, double *r, - Vector *start, Vector *finish) + Vector *start, Vector *finish) const { SBezier sb; if(!IsExtrusion(&sb, axis)) return false; @@ -63,18 +63,19 @@ bool SSurface::IsCylinder(Vector *axis, Vector *center, double *r, return true; } -SSurface SSurface::FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, - double thetas, double thetaf) -{ +// Create a surface patch by revolving and possibly translating a curve. +// Works for sections up to but not including 180 degrees. +SSurface SSurface::FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, double thetas, + double thetaf, double dists, + double distf) { // s is start, f is finish SSurface ret = {}; - - ret.degm = sb->deg; ret.degn = 2; double dtheta = fabs(WRAP_SYMMETRIC(thetaf - thetas, 2*PI)); + double w = cos(dtheta / 2); - // We now wish to revolve the curve about the z axis + // Revolve the curve about the z axis int i; for(i = 0; i <= ret.degm; i++) { Vector p = sb->ctrl[i]; @@ -82,32 +83,26 @@ SSurface SSurface::FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, Vector ps = p.RotatedAbout(pt, axis, thetas), pf = p.RotatedAbout(pt, axis, thetaf); - Vector ct; - if(ps.Equals(pf)) { - // Degenerate case: a control point lies on the axis of revolution, - // so we get three coincident control points. - ct = ps; - } else { - // Normal case, the control point sweeps out a circle. - Vector c = ps.ClosestPointOnLine(pt, axis); + // The middle control point should be at the intersection of the tangents at ps and pf. + // This is equivalent but works for 0 <= angle < 180 degrees. + Vector mid = ps.Plus(pf).ScaledBy(0.5); + Vector c = ps.ClosestPointOnLine(pt, axis); + Vector ct = mid.Minus(c).ScaledBy(1 / (w * w)).Plus(c); - Vector rs = ps.Minus(c), - rf = pf.Minus(c); - - Vector ts = axis.Cross(rs), - tf = axis.Cross(rf); - - ct = Vector::AtIntersectionOfLines(ps, ps.Plus(ts), - pf, pf.Plus(tf), - NULL, NULL, NULL); + // not sure this is needed + if(ps.Equals(pf)) { + ps = c; + ct = c; + pf = c; } - - ret.ctrl[i][0] = ps; - ret.ctrl[i][1] = ct; - ret.ctrl[i][2] = pf; + // moving along the axis can create hilical surfaces (or straight extrusion if + // thetas==thetaf) + ret.ctrl[i][0] = ps.Plus(axis.ScaledBy(dists)); + ret.ctrl[i][1] = ct.Plus(axis.ScaledBy((dists + distf) / 2)); + ret.ctrl[i][2] = pf.Plus(axis.ScaledBy(distf)); ret.weight[i][0] = sb->weight[i]; - ret.weight[i][1] = sb->weight[i]*cos(dtheta/2); + ret.weight[i][1] = sb->weight[i] * w; ret.weight[i][2] = sb->weight[i]; } @@ -131,12 +126,14 @@ SSurface SSurface::FromPlane(Vector pt, Vector u, Vector v) { return ret; } -SSurface SSurface::FromTransformationOf(SSurface *a, - Vector t, Quaternion q, double scale, +SSurface SSurface::FromTransformationOf(SSurface *a, Vector t, Quaternion q, double scale, bool includingTrims) { - SSurface ret = {}; + bool needRotate = !EXACT(q.vx == 0.0 && q.vy == 0.0 && q.vz == 0.0 && q.w == 1.0); + bool needTranslate = !EXACT(t.x == 0.0 && t.y == 0.0 && t.z == 0.0); + bool needScale = !EXACT(scale == 1.0); + SSurface ret = {}; ret.h = a->h; ret.color = a->color; ret.face = a->face; @@ -146,22 +143,38 @@ SSurface SSurface::FromTransformationOf(SSurface *a, int i, j; for(i = 0; i <= 3; i++) { for(j = 0; j <= 3; j++) { - ret.ctrl[i][j] = a->ctrl[i][j]; - ret.ctrl[i][j] = (ret.ctrl[i][j]).ScaledBy(scale); - ret.ctrl[i][j] = (q.Rotate(ret.ctrl[i][j])).Plus(t); - + Vector ctrl = a->ctrl[i][j]; + if(needScale) { + ctrl = ctrl.ScaledBy(scale); + } + if(needRotate) { + ctrl = q.Rotate(ctrl); + } + if(needTranslate) { + ctrl = ctrl.Plus(t); + } + ret.ctrl[i][j] = ctrl; ret.weight[i][j] = a->weight[i][j]; } } if(includingTrims) { STrimBy *stb; + ret.trim.ReserveMore(a->trim.n); for(stb = a->trim.First(); stb; stb = a->trim.NextAfter(stb)) { STrimBy n = *stb; - n.start = n.start.ScaledBy(scale); - n.finish = n.finish.ScaledBy(scale); - n.start = (q.Rotate(n.start)) .Plus(t); - n.finish = (q.Rotate(n.finish)).Plus(t); + if(needScale) { + n.start = n.start.ScaledBy(scale); + n.finish = n.finish.ScaledBy(scale); + } + if(needRotate) { + n.start = q.Rotate(n.start); + n.finish = q.Rotate(n.finish); + } + if(needTranslate) { + n.start = n.start.Plus(t); + n.finish = n.finish.Plus(t); + } ret.trim.Add(&n); } } @@ -175,7 +188,7 @@ SSurface SSurface::FromTransformationOf(SSurface *a, return ret; } -void SSurface::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) { +void SSurface::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const { *ptMax = Vector::From(VERY_NEGATIVE, VERY_NEGATIVE, VERY_NEGATIVE); *ptMin = Vector::From(VERY_POSITIVE, VERY_POSITIVE, VERY_POSITIVE); @@ -187,10 +200,10 @@ void SSurface::GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) { } } -bool SSurface::LineEntirelyOutsideBbox(Vector a, Vector b, bool segment) { +bool SSurface::LineEntirelyOutsideBbox(Vector a, Vector b, bool asSegment) const { Vector amax, amin; GetAxisAlignedBounding(&amax, &amin); - if(!Vector::BoundingBoxIntersectsLine(amax, amin, a, b, segment)) { + if(!Vector::BoundingBoxIntersectsLine(amax, amin, a, b, asSegment)) { // The line segment could fail to intersect the bbox, but lie entirely // within it and intersect the surface. if(a.OutsideAndNotOn(amax, amin) && b.OutsideAndNotOn(amax, amin)) { @@ -204,7 +217,7 @@ bool SSurface::LineEntirelyOutsideBbox(Vector a, Vector b, bool segment) { // Generate the piecewise linear approximation of the trim stb, which applies // to the curve sc. //----------------------------------------------------------------------------- -void SSurface::MakeTrimEdgesInto(SEdgeList *sel, int flags, +void SSurface::MakeTrimEdgesInto(SEdgeList *sel, MakeAs flags, SCurve *sc, STrimBy *stb) { Vector prev = Vector::From(0, 0, 0); @@ -222,9 +235,9 @@ void SSurface::MakeTrimEdgesInto(SEdgeList *sel, int flags, increment = 1; } for(i = first; i != (last + increment); i += increment) { - Vector tpt, *pt = &(sc->pts.elem[i].p); + Vector tpt, *pt = &(sc->pts[i].p); - if(flags & AS_UV) { + if(flags == MakeAs::UV) { ClosestPointTo(*pt, &u, &v); tpt = Vector::From(u, v, 0); } else { @@ -251,7 +264,7 @@ void SSurface::MakeTrimEdgesInto(SEdgeList *sel, int flags, // the split curves from useCurvesFrom instead of the curves in our own // shell. //----------------------------------------------------------------------------- -void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, int flags, +void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, MakeAs flags, SShell *useCurvesFrom) { STrimBy *stb; @@ -275,8 +288,7 @@ void SSurface::MakeEdgesInto(SShell *shell, SEdgeList *sel, int flags, // by taking the cross product of the surface normals. We choose the direction // of this tangent so that its dot product with dir is positive. //----------------------------------------------------------------------------- -Vector SSurface::ExactSurfaceTangentAt(Vector p, SSurface *srfA, SSurface *srfB, - Vector dir) +Vector SSurface::ExactSurfaceTangentAt(Vector p, SSurface *srfA, SSurface *srfB, Vector dir) { Point2d puva, puvb; srfA->ClosestPointTo(p, &puva); @@ -295,8 +307,7 @@ Vector SSurface::ExactSurfaceTangentAt(Vector p, SSurface *srfA, SSurface *srfB, // add its exact form to sbl. Otherwise, add its piecewise linearization to // sel. //----------------------------------------------------------------------------- -void SSurface::MakeSectionEdgesInto(SShell *shell, - SEdgeList *sel, SBezierList *sbl) +void SSurface::MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList *sbl) { STrimBy *stb; for(stb = trim.First(); stb; stb = trim.NextAfter(stb)) { @@ -324,19 +335,19 @@ void SSurface::MakeSectionEdgesInto(SShell *shell, sbl->l.Add(&keep_bef); } else if(sbl && !sel && !sc->isExact) { // We must approximate this trim curve, as piecewise cubic sections. - SSurface *srfA = shell->surface.FindById(sc->surfA), - *srfB = shell->surface.FindById(sc->surfB); + SSurface *srfA = shell->surface.FindById(sc->surfA); + SSurface *srfB = shell->surface.FindById(sc->surfB); Vector s = stb->backwards ? stb->finish : stb->start, f = stb->backwards ? stb->start : stb->finish; int sp, fp; for(sp = 0; sp < sc->pts.n; sp++) { - if(s.Equals(sc->pts.elem[sp].p)) break; + if(s.Equals(sc->pts[sp].p)) break; } if(sp >= sc->pts.n) return; for(fp = sp; fp < sc->pts.n; fp++) { - if(f.Equals(sc->pts.elem[fp].p)) break; + if(f.Equals(sc->pts[fp].p)) break; } if(fp >= sc->pts.n) return; // So now the curve we want goes from elem[sp] to elem[fp] @@ -349,8 +360,8 @@ void SSurface::MakeSectionEdgesInto(SShell *shell, for(;;) { // So construct a cubic Bezier with the correct endpoints // and tangents for the current span. - Vector st = sc->pts.elem[sp].p, - ft = sc->pts.elem[fpt].p, + Vector st = sc->pts[sp].p, + ft = sc->pts[fpt].p, sf = ft.Minus(st); double m = sf.Magnitude() / 3; @@ -367,9 +378,9 @@ void SSurface::MakeSectionEdgesInto(SShell *shell, int i; bool tooFar = false; for(i = sp + 1; i <= (fpt - 1); i++) { - Vector p = sc->pts.elem[i].p; + Vector p = sc->pts[i].p; double t; - sb.ClosestPointTo(p, &t, false); + sb.ClosestPointTo(p, &t, /*mustConverge=*/false); Vector pp = sb.PointAt(t); if((pp.Minus(p)).Magnitude() > SS.ChordTolMm()/2) { tooFar = true; @@ -393,7 +404,7 @@ void SSurface::MakeSectionEdgesInto(SShell *shell, sp = fpt; } } else { - if(sel) MakeTrimEdgesInto(sel, AS_XYZ, sc, stb); + if(sel) MakeTrimEdgesInto(sel, MakeAs::XYZ, sc, stb); } } } @@ -401,10 +412,10 @@ void SSurface::MakeSectionEdgesInto(SShell *shell, void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { SEdgeList el = {}; - MakeEdgesInto(shell, &el, AS_UV); + MakeEdgesInto(shell, &el, MakeAs::UV); SPolygon poly = {}; - if(el.AssemblePolygon(&poly, NULL, true)) { + if(el.AssemblePolygon(&poly, NULL, /*keepDir=*/true)) { int i, start = sm->l.n; if(degm == 1 && degn == 1) { // A surface with curvature along one direction only; so @@ -424,9 +435,8 @@ void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { STriMeta meta = { face, color }; for(i = start; i < sm->l.n; i++) { - STriangle *st = &(sm->l.elem[i]); + STriangle *st = &(sm->l[i]); st->meta = meta; - if(st->meta.color.alpha != 255) sm->isTransparent = true; st->an = NormalAt(st->a.x, st->a.y); st->bn = NormalAt(st->b.x, st->b.y); st->cn = NormalAt(st->c.x, st->c.y); @@ -450,7 +460,7 @@ void SSurface::TriangulateInto(SShell *shell, SMesh *sm) { // normal. We therefore must reverse all our trim curves too. The uv // coordinates change, but trim curves are stored as xyz so nothing happens //----------------------------------------------------------------------------- -void SSurface::Reverse(void) { +void SSurface::Reverse() { int i, j; for(i = 0; i < (degm+1)/2; i++) { for(j = 0; j <= degn; j++) { @@ -475,7 +485,7 @@ void SSurface::ScaleSelfBy(double s) { } } -void SSurface::Clear(void) { +void SSurface::Clear() { trim.Clear(); } @@ -497,9 +507,9 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, Rgb Vector n = sbls->normal.ScaledBy(-1); Vector u = n.Normal(0), v = n.Normal(1); Vector orig = sbls->point; - double umax = 1e-10, umin = 1e10; + double umax = VERY_NEGATIVE, umin = VERY_POSITIVE; sbls->GetBoundingProjd(u, orig, &umin, &umax); - double vmax = 1e-10, vmin = 1e10; + double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE; sbls->GetBoundingProjd(v, orig, &vmin, &vmax); // and now fix things up so that all u and v lie between 0 and 1 orig = orig.Plus(u.ScaledBy(umin)); @@ -551,14 +561,14 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, Rgb STrimBy stb0, stb1; // The translated curves trim the flat top and bottom surfaces. - stb0 = STrimBy::EntireCurve(this, hc0, false); - stb1 = STrimBy::EntireCurve(this, hc1, true); + stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/false); + stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/true); (surface.FindById(hs0))->trim.Add(&stb0); (surface.FindById(hs1))->trim.Add(&stb1); // The translated curves also trim the surface of extrusion. - stb0 = STrimBy::EntireCurve(this, hc0, true); - stb1 = STrimBy::EntireCurve(this, hc1, false); + stb0 = STrimBy::EntireCurve(this, hc0, /*backwards=*/true); + stb1 = STrimBy::EntireCurve(this, hc1, /*backwards=*/false); (surface.FindById(hsext))->trim.Add(&stb0); (surface.FindById(hsext))->trim.Add(&stb1); @@ -578,15 +588,15 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, Rgb int i; for(i = 0; i < trimLines.n; i++) { - TrimLine *tl = &(trimLines.elem[i]); + TrimLine *tl = &(trimLines[i]); SSurface *ss = surface.FindById(tl->hs); - TrimLine *tlp = &(trimLines.elem[WRAP(i-1, trimLines.n)]); + TrimLine *tlp = &(trimLines[WRAP(i-1, trimLines.n)]); STrimBy stb; - stb = STrimBy::EntireCurve(this, tl->hc, true); + stb = STrimBy::EntireCurve(this, tl->hc, /*backwards=*/true); ss->trim.Add(&stb); - stb = STrimBy::EntireCurve(this, tlp->hc, false); + stb = STrimBy::EntireCurve(this, tlp->hc, /*backwards=*/false); ss->trim.Add(&stb); (curve.FindById(tl->hc))->surfA = ss->h; @@ -596,20 +606,11 @@ void SShell::MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, Rgb } } - -typedef struct { - hSSurface d[4]; -} Revolved; - -void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color, Group *group) +bool SShell::CheckNormalAxisRelationship(SBezierLoopSet *sbls, Vector pt, Vector axis, double da, double dx) +// Check that the direction of revolution/extrusion ends up parallel to the normal of +// the sketch, on the side of the axis where the sketch is. { SBezierLoop *sbl; - - int i0 = surface.n, i; - - // Normalize the axis direction so that the direction of revolution - // ends up parallel to the normal of the sketch, on the side of the - // axis where the sketch is. Vector pto; double md = VERY_NEGATIVE; for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { @@ -619,18 +620,230 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, // if we choose a point that lies on the axis, for example. // (And our surface will be self-intersecting if the sketch // spans the axis, so don't worry about that.) - Vector p = sb->Start(); - double d = p.DistanceToLine(pt, axis); - if(d > md) { - md = d; - pto = p; + for(int i = 0; i <= sb->deg; i++) { + Vector p = sb->ctrl[i]; + double d = p.DistanceToLine(pt, axis); + if(d > md) { + md = d; + pto = p; + } } } } Vector ptc = pto.ClosestPointOnLine(pt, axis), - up = (pto.Minus(ptc)).WithMagnitude(1), - vp = (sbls->normal).Cross(up); - if(vp.Dot(axis) < 0) { + up = axis.Cross(pto.Minus(ptc)).ScaledBy(da), + vp = up.Plus(axis.ScaledBy(dx)); + + return (vp.Dot(sbls->normal) > 0); +} + +// sketch must not contain the axis of revolution as a non-construction line for helix +void SShell::MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, + RgbaColor color, Group *group, double angles, + double anglef, double dists, double distf) { + int i0 = surface.n; // number of pre-existing surfaces + SBezierLoop *sbl; + // for testing - hard code the axial distance, and number of sections. + // distance will need to be parameters in the future. + double dist = distf - dists; + int sections = (int)(fabs(anglef - angles) / (PI / 2) + 1); + double wedge = (anglef - angles) / sections; + int startMapping = Group::REMAP_LATHE_START, endMapping = Group::REMAP_LATHE_END; + + if(CheckNormalAxisRelationship(sbls, pt, axis, anglef-angles, distf-dists)) { + swap(angles, anglef); + swap(dists, distf); + dist = -dist; + wedge = -wedge; + swap(startMapping, endMapping); + } + + // Define a coordinate system to contain the original sketch, and get + // a bounding box in that csys + Vector n = sbls->normal.ScaledBy(-1); + Vector u = n.Normal(0), v = n.Normal(1); + Vector orig = sbls->point; + double umax = VERY_NEGATIVE, umin = VERY_POSITIVE; + sbls->GetBoundingProjd(u, orig, &umin, &umax); + double vmax = VERY_NEGATIVE, vmin = VERY_POSITIVE; + sbls->GetBoundingProjd(v, orig, &vmin, &vmax); + // and now fix things up so that all u and v lie between 0 and 1 + orig = orig.Plus(u.ScaledBy(umin)); + orig = orig.Plus(v.ScaledBy(vmin)); + u = u.ScaledBy(umax - umin); + v = v.ScaledBy(vmax - vmin); + + // So we can now generate the end caps of the extrusion within + // a translated and rotated (and maybe mirrored) version of that csys. + SSurface s0, s1; + s0 = SSurface::FromPlane(orig.RotatedAbout(pt, axis, angles).Plus(axis.ScaledBy(dists)), + u.RotatedAbout(axis, angles), v.RotatedAbout(axis, angles)); + s0.color = color; + + hEntity face0 = group->Remap(Entity::NO_ENTITY, startMapping); + s0.face = face0.v; + + s1 = SSurface::FromPlane( + orig.Plus(u).RotatedAbout(pt, axis, anglef).Plus(axis.ScaledBy(distf)), + u.ScaledBy(-1).RotatedAbout(axis, anglef), v.RotatedAbout(axis, anglef)); + s1.color = color; + + hEntity face1 = group->Remap(Entity::NO_ENTITY, endMapping); + s1.face = face1.v; + + hSSurface hs0 = surface.AddAndAssignId(&s0); + hSSurface hs1 = surface.AddAndAssignId(&s1); + + // Now we actually build and trim the swept surfaces. One loop at a time. + for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { + int i, j; + SBezier *sb; + List> hsl = {}; + + // This is where all the NURBS are created and Remapped to the generating curve + for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { + std::vector revs(sections); + for(j = 0; j < sections; j++) { + if((dist == 0) && sb->deg == 1 && + (sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS && + (sb->ctrl[1]).DistanceToLine(pt, axis) < LENGTH_EPS) { + // This is a line on the axis of revolution; it does + // not contribute a surface. + revs[j].v = 0; + } else { + SSurface ss = SSurface::FromRevolutionOf( + sb, pt, axis, angles + (wedge)*j, angles + (wedge) * (j + 1), + dists + j * dist / sections, dists + (j + 1) * dist / sections); + ss.color = color; + if(sb->entity != 0) { + hEntity he; + he.v = sb->entity; + hEntity hface = group->Remap(he, Group::REMAP_LINE_TO_FACE); + if(SK.entity.FindByIdNoOops(hface) != NULL) { + ss.face = hface.v; + } + } + revs[j] = surface.AddAndAssignId(&ss); + } + } + hsl.Add(&revs); + } + // Still the same loop. Need to create trim curves + for(i = 0; i < sbl->l.n; i++) { + std::vector revs = hsl[i], revsp = hsl[WRAP(i - 1, sbl->l.n)]; + + sb = &(sbl->l[i]); + + // we will need the grid t-values for this entire row of surfaces + List t_values; + t_values = {}; + if (revs[0].v) { + double ps = 0.0; + t_values.Add(&ps); + (surface.FindById(revs[0]))->MakeTriangulationGridInto( + &t_values, 0.0, 1.0, true, 0); + } + // we generate one more curve than we did surfaces + for(j = 0; j <= sections; j++) { + SCurve sc; + Quaternion qs = Quaternion::From(axis, angles + wedge * j); + // we want Q*(x - p) + p = Q*x + (p - Q*p) + Vector ts = + pt.Minus(qs.Rotate(pt)).Plus(axis.ScaledBy(dists + j * dist / sections)); + + // If this input curve generated a surface, then trim that + // surface with the rotated version of the input curve. + if(revs[0].v) { // not d[j] because crash on j==sections + sc = {}; + sc.isExact = true; + sc.exact = sb->TransformedBy(ts, qs, 1.0); + // make the PWL for the curve based on t value list + for(int x = 0; x < t_values.n; x++) { + SCurvePt scpt; + scpt.tag = 0; + scpt.p = sc.exact.PointAt(t_values[x]); + scpt.vertex = (x == 0) || (x == (t_values.n - 1)); + sc.pts.Add(&scpt); + } + + // the surfaces already exists so trim with this curve + if(j < sections) { + sc.surfA = revs[j]; + } else { + sc.surfA = hs1; // end cap + } + + if(j > 0) { + sc.surfB = revs[j - 1]; + } else { + sc.surfB = hs0; // staring cap + } + + hSCurve hcb = curve.AddAndAssignId(&sc); + + STrimBy stb; + stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true); + (surface.FindById(sc.surfA))->trim.Add(&stb); + stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false); + (surface.FindById(sc.surfB))->trim.Add(&stb); + } else if(j == 0) { // curve was on the rotation axis and is shared by the end caps. + sc = {}; + sc.isExact = true; + sc.exact = sb->TransformedBy(ts, qs, 1.0); + (sc.exact).MakePwlInto(&(sc.pts)); + sc.surfA = hs1; // end cap + sc.surfB = hs0; // staring cap + hSCurve hcb = curve.AddAndAssignId(&sc); + + STrimBy stb; + stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true); + (surface.FindById(sc.surfA))->trim.Add(&stb); + stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false); + (surface.FindById(sc.surfB))->trim.Add(&stb); + } + + // And if this input curve and the one after it both generated + // surfaces, then trim both of those by the appropriate + // curve based on the control points. + if((j < sections) && revs[j].v && revsp[j].v) { + SSurface *ss = surface.FindById(revs[j]); + + sc = {}; + sc.isExact = true; + sc.exact = SBezier::From(ss->ctrl[0][0], ss->ctrl[0][1], ss->ctrl[0][2]); + sc.exact.weight[1] = ss->weight[0][1]; + double max_dt = 0.5; + if (sc.exact.deg > 1) max_dt = 0.125; + (sc.exact).MakePwlInto(&(sc.pts), 0.0, max_dt); + sc.surfA = revs[j]; + sc.surfB = revsp[j]; + + hSCurve hcc = curve.AddAndAssignId(&sc); + + STrimBy stb; + stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false); + (surface.FindById(sc.surfA))->trim.Add(&stb); + stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true); + (surface.FindById(sc.surfB))->trim.Add(&stb); + } + } + t_values.Clear(); + } + + hsl.Clear(); + } + + if(dist == 0) { + MakeFirstOrderRevolvedSurfaces(pt, axis, i0); + } +} + +void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color, + Group *group) { + int i0 = surface.n; // number of pre-existing surfaces + SBezierLoop *sbl; + + if(CheckNormalAxisRelationship(sbls, pt, axis, 1.0, 0.0)) { axis = axis.ScaledBy(-1); } @@ -638,10 +851,10 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, for(sbl = sbls->l.First(); sbl; sbl = sbls->l.NextAfter(sbl)) { int i, j; SBezier *sb; - List hsl = {}; + List> hsl = {}; for(sb = sbl->l.First(); sb; sb = sbl->l.NextAfter(sb)) { - Revolved revs; + std::vector revs(4); for(j = 0; j < 4; j++) { if(sb->deg == 1 && (sb->ctrl[0]).DistanceToLine(pt, axis) < LENGTH_EPS && @@ -649,11 +862,10 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, { // This is a line on the axis of revolution; it does // not contribute a surface. - revs.d[j].v = 0; + revs[j].v = 0; } else { - SSurface ss = SSurface::FromRevolutionOf(sb, pt, axis, - (PI/2)*j, - (PI/2)*(j+1)); + SSurface ss = SSurface::FromRevolutionOf(sb, pt, axis, (PI / 2) * j, + (PI / 2) * (j + 1), 0.0, 0.0); ss.color = color; if(sb->entity != 0) { hEntity he; @@ -663,17 +875,17 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, ss.face = hface.v; } } - revs.d[j] = surface.AddAndAssignId(&ss); + revs[j] = surface.AddAndAssignId(&ss); } } hsl.Add(&revs); } for(i = 0; i < sbl->l.n; i++) { - Revolved revs = hsl.elem[i], - revsp = hsl.elem[WRAP(i-1, sbl->l.n)]; + std::vector revs = hsl[i], + revsp = hsl[WRAP(i-1, sbl->l.n)]; - sb = &(sbl->l.elem[i]); + sb = &(sbl->l[i]); for(j = 0; j < 4; j++) { SCurve sc; @@ -683,28 +895,28 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, // If this input curve generate a surface, then trim that // surface with the rotated version of the input curve. - if(revs.d[j].v) { + if(revs[j].v) { sc = {}; sc.isExact = true; sc.exact = sb->TransformedBy(ts, qs, 1.0); (sc.exact).MakePwlInto(&(sc.pts)); - sc.surfA = revs.d[j]; - sc.surfB = revs.d[WRAP(j-1, 4)]; + sc.surfA = revs[j]; + sc.surfB = revs[WRAP(j-1, 4)]; hSCurve hcb = curve.AddAndAssignId(&sc); STrimBy stb; - stb = STrimBy::EntireCurve(this, hcb, true); + stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/true); (surface.FindById(sc.surfA))->trim.Add(&stb); - stb = STrimBy::EntireCurve(this, hcb, false); + stb = STrimBy::EntireCurve(this, hcb, /*backwards=*/false); (surface.FindById(sc.surfB))->trim.Add(&stb); } // And if this input curve and the one after it both generated // surfaces, then trim both of those by the appropriate // circle. - if(revs.d[j].v && revsp.d[j].v) { - SSurface *ss = surface.FindById(revs.d[j]); + if(revs[j].v && revsp[j].v) { + SSurface *ss = surface.FindById(revs[j]); sc = {}; sc.isExact = true; @@ -713,15 +925,15 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, ss->ctrl[0][2]); sc.exact.weight[1] = ss->weight[0][1]; (sc.exact).MakePwlInto(&(sc.pts)); - sc.surfA = revs.d[j]; - sc.surfB = revsp.d[j]; + sc.surfA = revs[j]; + sc.surfB = revsp[j]; hSCurve hcc = curve.AddAndAssignId(&sc); STrimBy stb; - stb = STrimBy::EntireCurve(this, hcc, false); + stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/false); (surface.FindById(sc.surfA))->trim.Add(&stb); - stb = STrimBy::EntireCurve(this, hcc, true); + stb = STrimBy::EntireCurve(this, hcc, /*backwards=*/true); (surface.FindById(sc.surfB))->trim.Add(&stb); } } @@ -730,8 +942,14 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, hsl.Clear(); } + MakeFirstOrderRevolvedSurfaces(pt, axis, i0); +} + +void SShell::MakeFirstOrderRevolvedSurfaces(Vector pt, Vector axis, int i0) { + int i; + for(i = i0; i < surface.n; i++) { - SSurface *srf = &(surface.elem[i]); + SSurface *srf = &(surface[i]); // Revolution of a line; this is potentially a plane, which we can // rewrite to have degree (1, 1). @@ -806,12 +1024,11 @@ void SShell::MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, continue; } } - } - } void SShell::MakeFromCopyOf(SShell *a) { + ssassert(this != a, "Can't make from copy of self"); MakeFromTransformationOf(a, Vector::From(0, 0, 0), Quaternion::IDENTITY, 1.0); } @@ -820,14 +1037,15 @@ void SShell::MakeFromTransformationOf(SShell *a, Vector t, Quaternion q, double scale) { booleanFailed = false; - + surface.ReserveMore(a->surface.n); SSurface *s; for(s = a->surface.First(); s; s = a->surface.NextAfter(s)) { SSurface n; - n = SSurface::FromTransformationOf(s, t, q, scale, true); + n = SSurface::FromTransformationOf(s, t, q, scale, /*includingTrims=*/true); surface.Add(&n); // keeping the old ID } + curve.ReserveMore(a->curve.n); SCurve *c; for(c = a->curve.First(); c; c = a->curve.NextAfter(c)) { SCurve n; @@ -839,12 +1057,11 @@ void SShell::MakeFromTransformationOf(SShell *a, void SShell::MakeEdgesInto(SEdgeList *sel) { SSurface *s; for(s = surface.First(); s; s = surface.NextAfter(s)) { - s->MakeEdgesInto(this, sel, SSurface::AS_XYZ); + s->MakeEdgesInto(this, sel, SSurface::MakeAs::XYZ); } } -void SShell::MakeSectionEdgesInto(Vector n, double d, - SEdgeList *sel, SBezierList *sbl) +void SShell::MakeSectionEdgesInto(Vector n, double d, SEdgeList *sel, SBezierList *sbl) { SSurface *s; for(s = surface.First(); s; s = surface.NextAfter(s)) { @@ -855,17 +1072,22 @@ void SShell::MakeSectionEdgesInto(Vector n, double d, } void SShell::TriangulateInto(SMesh *sm) { - SSurface *s; - for(s = surface.First(); s; s = surface.NextAfter(s)) { - s->TriangulateInto(this, sm); +#pragma omp parallel for + for(int i=0; iTriangulateInto(this, &m); + #pragma omp critical + sm->MakeFromCopyOf(&m); + m.Clear(); } } -bool SShell::IsEmpty(void) { - return (surface.n == 0); +bool SShell::IsEmpty() const { + return surface.IsEmpty(); } -void SShell::Clear(void) { +void SShell::Clear() { SSurface *s; for(s = surface.First(); s; s = surface.NextAfter(s)) { s->Clear(); @@ -878,4 +1100,3 @@ void SShell::Clear(void) { } curve.Clear(); } - diff --git a/src/srf/surface.h b/src/srf/surface.h index 86d69db..5497455 100644 --- a/src/srf/surface.h +++ b/src/srf/surface.h @@ -7,13 +7,10 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#ifndef __SURFACE_H -#define __SURFACE_H - -// Utility functions, Bernstein polynomials of order 1-3 and their derivatives. -double Bernstein(int k, int deg, double t); -double BernsteinDerivative(int k, int deg, double t); +#ifndef SOLVESPACE_SURFACE_H +#define SOLVESPACE_SURFACE_H +class SBezierList; class SSurface; class SCurvePt; @@ -28,7 +25,7 @@ public: SBspUv *more; - enum { + enum class Class : uint32_t { INSIDE = 100, OUTSIDE = 200, EDGE_PARALLEL = 300, @@ -36,20 +33,20 @@ public: EDGE_OTHER = 500 }; - static SBspUv *Alloc(void); + static SBspUv *Alloc(); static SBspUv *From(SEdgeList *el, SSurface *srf); - void ScalePoints(Point2d *pt, Point2d *a, Point2d *b, SSurface *srf); + void ScalePoints(Point2d *pt, Point2d *a, Point2d *b, SSurface *srf) const; double ScaledSignedDistanceToLine(Point2d pt, Point2d a, Point2d b, - SSurface *srf); - double ScaledDistanceToLine(Point2d pt, Point2d a, Point2d b, bool seg, - SSurface *srf); + SSurface *srf) const; + double ScaledDistanceToLine(Point2d pt, Point2d a, Point2d b, bool asSegment, + SSurface *srf) const; void InsertEdge(Point2d a, Point2d b, SSurface *srf); - static SBspUv *InsertOrCreateEdge(SBspUv *where, const Point2d &ea, const Point2d &eb, SSurface *srf); - int ClassifyPoint(Point2d p, Point2d eb, SSurface *srf); - int ClassifyEdge(Point2d ea, Point2d eb, SSurface *srf); - double MinimumDistanceToEdge(Point2d p, SSurface *srf); + static SBspUv *InsertOrCreateEdge(SBspUv *where, Point2d ea, Point2d eb, SSurface *srf); + Class ClassifyPoint(Point2d p, Point2d eb, SSurface *srf) const; + Class ClassifyEdge(Point2d ea, Point2d eb, SSurface *srf) const; + double MinimumDistanceToEdge(Point2d p, SSurface *srf) const; }; // Now the data structures to represent a shell of trimmed rational polynomial @@ -62,11 +59,17 @@ public: uint32_t v; }; +template<> +struct IsHandleOracle : std::true_type {}; + class hSCurve { public: uint32_t v; }; +template<> +struct IsHandleOracle : std::true_type {}; + // Stuff for rational polynomial curves, of degree one to three. These are // our inputs, and are also calculated for certain exact surface-surface // intersections. @@ -80,33 +83,34 @@ public: double weight[4]; uint32_t entity; - Vector PointAt(double t); - Vector TangentAt(double t); - void ClosestPointTo(Vector p, double *t, bool converge=true); - void SplitAt(double t, SBezier *bef, SBezier *aft); - bool PointOnThisAndCurve(SBezier *sbb, Vector *p); - - Vector Start(void); - Vector Finish(void); - bool Equals(SBezier *b); - void MakePwlInto(SEdgeList *sel, double chordTol=0); - void MakePwlInto(List *l, double chordTol=0); - void MakePwlInto(SContour *sc, double chordTol=0); - void MakePwlInto(List *l, double chordTol=0); - void MakePwlWorker(List *l, double ta, double tb, double chordTol); - void MakePwlInitialWorker(List *l, double ta, double tb, double chordTol); - - void AllIntersectionsWith(SBezier *sbb, SPointList *spl); - void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax); - void Reverse(void); - - bool IsInPlane(Vector n, double d); - bool IsCircle(Vector axis, Vector *center, double *r); - bool IsRational(void); - - SBezier TransformedBy(Vector t, Quaternion q, double scale); + Vector PointAt(double t) const; + Vector TangentAt(double t) const; + void ClosestPointTo(Vector p, double *t, bool mustConverge=true) const; + void SplitAt(double t, SBezier *bef, SBezier *aft) const; + bool PointOnThisAndCurve(const SBezier *sbb, Vector *p) const; + + Vector Start() const; + Vector Finish() const; + bool Equals(SBezier *b) const; + void MakePwlInto(SEdgeList *sel, double chordTol=0, double max_dt=0.0) const; + void MakePwlInto(List *l, double chordTol=0, double max_dt=0.0) const; + void MakePwlInto(SContour *sc, double chordTol=0, double max_dt=0.0) const; + void MakePwlInto(List *l, double chordTol=0, double max_dt=0.0) const; + void MakePwlWorker(List *l, double ta, double tb, double chordTol, double max_dt) const; + void MakePwlInitialWorker(List *l, double ta, double tb, double chordTol, double max_dt) const; + void MakeNonrationalCubicInto(SBezierList *bl, double tolerance, int depth = 0) const; + + void AllIntersectionsWith(const SBezier *sbb, SPointList *spl) const; + void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const; + void Reverse(); + + bool IsInPlane(Vector n, double d) const; + bool IsCircle(Vector axis, Vector *center, double *r) const; + bool IsRational() const; + + SBezier TransformedBy(Vector t, Quaternion q, double scale) const; SBezier InPerspective(Vector u, Vector v, Vector n, - Vector origin, double cameraTan); + Vector origin, double cameraTan) const; void ScaleSelfBy(double s); static SBezier From(Vector p0, Vector p1, Vector p2, Vector p3); @@ -121,12 +125,12 @@ class SBezierList { public: List l; - void Clear(void); + void Clear(); void ScaleSelfBy(double s); - void CullIdenticalBeziers(void); - void AllIntersectionsWith(SBezierList *sblb, SPointList *spl); + void CullIdenticalBeziers(bool both=true); + void AllIntersectionsWith(SBezierList *sblb, SPointList *spl) const; bool GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v, - Vector *notCoplanarAt); + Vector *notCoplanarAt) const; }; class SBezierLoop { @@ -134,11 +138,11 @@ public: int tag; List l; - inline void Clear(void) { l.Clear(); } - bool IsClosed(void); - void Reverse(void); - void MakePwlInto(SContour *sc, double chordTol=0); - void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax); + inline void Clear() { l.Clear(); } + bool IsClosed() const; + void Reverse(); + void MakePwlInto(SContour *sc, double chordTol=0) const; + void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const; static SBezierLoop FromCurves(SBezierList *spcl, bool *allClosed, SEdge *errorAt); @@ -149,15 +153,17 @@ public: List l; Vector normal; Vector point; + double area; static SBezierLoopSet From(SBezierList *spcl, SPolygon *poly, double chordTol, bool *allClosed, SEdge *errorAt, - SBezierList *openContours); + SBezierLoopSet *openContours); - void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax); - void MakePwlInto(SPolygon *sp); - void Clear(void); + void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const; + double SignedArea(); + void MakePwlInto(SPolygon *sp) const; + void Clear(); }; class SBezierLoopSetSet { @@ -168,9 +174,9 @@ public: double chordTol, bool *allClosed, SEdge *notClosedAt, bool *allCoplanar, Vector *notCoplanarAt, - SBezierList *openContours); + SBezierLoopSet *openContours); void AddOpenPath(SBezier *sb); - void Clear(void); + void Clear(); }; // Stuff for the surface trim curves: piecewise linear @@ -189,12 +195,12 @@ public: // therefore must get new hSCurves assigned. For the curves in A and B, // we use newH to record their new handle in C. hSCurve newH; - enum { - FROM_A = 100, - FROM_B = 200, - FROM_INTERSECTION = 300 + enum class Source : uint32_t { + A = 100, + B = 200, + INTERSECTION = 300 }; - int source; + Source source; bool isExact; SBezier exact; @@ -204,15 +210,16 @@ public: hSSurface surfA; hSSurface surfB; - static SCurve FromTransformationOf(SCurve *a, Vector t, Quaternion q, - double scale); + static SCurve FromTransformationOf(SCurve *a, Vector t, + Quaternion q, double scale); SCurve MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, - SSurface *srfA, SSurface *srfB); + SSurface *srfA, SSurface *srfB) const; void RemoveShortSegments(SSurface *srfA, SSurface *srfB); - SSurface *GetSurfaceA(SShell *a, SShell *b); - SSurface *GetSurfaceB(SShell *a, SShell *b); + SSurface *GetSurfaceA(SShell *a, SShell *b) const; + SSurface *GetSurfaceB(SShell *a, SShell *b) const; - void Clear(void); + void Clear(); + void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const; }; // A segment of a curve by which a surface is trimmed: indicates which curve, @@ -229,7 +236,7 @@ public: Vector start; Vector finish; - static STrimBy EntireCurve(SShell *shell, hSCurve hsc, bool bkwds); + static STrimBy EntireCurve(SShell *shell, hSCurve hsc, bool backwards); }; // An intersection point between a line and a surface @@ -246,6 +253,13 @@ public: // A rational polynomial surface in Bezier form. class SSurface { public: + + enum class CombineAs : uint32_t { + UNION = 10, + DIFFERENCE = 11, + INTERSECTION = 12 + }; + int tag; hSSurface h; @@ -271,8 +285,8 @@ public: Point2d cached; static SSurface FromExtrusionOf(SBezier *spc, Vector t0, Vector t1); - static SSurface FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, - double thetas, double thetaf); + static SSurface FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, double thetas, + double thetaf, double dists, double distf); static SSurface FromPlane(Vector pt, Vector u, Vector v); static SSurface FromTransformationOf(SSurface *a, Vector t, Quaternion q, double scale, @@ -286,7 +300,7 @@ public: SShell *shell, SShell *sha, SShell *shb); void FindChainAvoiding(SEdgeList *src, SEdgeList *dest, SPointList *avoid); SSurface MakeCopyTrimAgainst(SShell *parent, SShell *a, SShell *b, - SShell *into, int type); + SShell *into, SSurface::CombineAs type, int dbg_index); void TrimFromEdgeList(SEdgeList *el, bool asUv); void IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, SShell *into); @@ -297,63 +311,66 @@ public: int tag; Point2d p; } Inter; - void WeightControlPoints(void); - void UnWeightControlPoints(void); + void WeightControlPoints(); + void UnWeightControlPoints(); void CopyRowOrCol(bool row, int this_ij, SSurface *src, int src_ij); void BlendRowOrCol(bool row, int this_ij, SSurface *a, int a_ij, SSurface *b, int b_ij); - double DepartureFromCoplanar(void); + double DepartureFromCoplanar() const; void SplitInHalf(bool byU, SSurface *sa, SSurface *sb); void AllPointsIntersecting(Vector a, Vector b, - List *l, - bool seg, bool trimmed, bool inclTangent); + List *l, + bool asSegment, bool trimmed, bool inclTangent); void AllPointsIntersectingUntrimmed(Vector a, Vector b, - int *cnt, int *level, - List *l, bool segment, - SSurface *sorig); + int *cnt, int *level, + List *l, bool asSegment, + SSurface *sorig); - void ClosestPointTo(Vector p, Point2d *puv, bool converge=true); - void ClosestPointTo(Vector p, double *u, double *v, bool converge=true); - bool ClosestPointNewton(Vector p, double *u, double *v, bool converge=true); + void ClosestPointTo(Vector p, Point2d *puv, bool mustConverge=true); + void ClosestPointTo(Vector p, double *u, double *v, bool mustConverge=true); + bool ClosestPointNewton(Vector p, double *u, double *v, bool mustConverge=true) const; - bool PointIntersectingLine(Vector p0, Vector p1, double *u, double *v); + bool PointIntersectingLine(Vector p0, Vector p1, double *u, double *v) const; Vector ClosestPointOnThisAndSurface(SSurface *srf2, Vector p); void PointOnSurfaces(SSurface *s1, SSurface *s2, double *u, double *v); - Vector PointAt(double u, double v); - Vector PointAt(Point2d puv); - void TangentsAt(double u, double v, Vector *tu, Vector *tv); - Vector NormalAt(Point2d puv); - Vector NormalAt(double u, double v); - bool LineEntirelyOutsideBbox(Vector a, Vector b, bool segment); - void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin); - bool CoincidentWithPlane(Vector n, double d); - bool CoincidentWith(SSurface *ss, bool sameNormal); - bool IsExtrusion(SBezier *of, Vector *along); + void PointOnCurve(const SBezier *curve, double *up, double *vp); + Vector PointAt(double u, double v) const; + Vector PointAt(Point2d puv) const; + void TangentsAt(double u, double v, Vector *tu, Vector *tv, bool retry=true) const; + Vector NormalAt(Point2d puv) const; + Vector NormalAt(double u, double v) const; + bool LineEntirelyOutsideBbox(Vector a, Vector b, bool asSegment) const; + void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const; + bool CoincidentWithPlane(Vector n, double d) const; + bool CoincidentWith(SSurface *ss, bool sameNormal) const; + bool ContainsPlaneCurve(SCurve *sc) const; + bool IsExtrusion(SBezier *of, Vector *along) const; bool IsCylinder(Vector *axis, Vector *center, double *r, - Vector *start, Vector *finish); + Vector *start, Vector *finish) const; void TriangulateInto(SShell *shell, SMesh *sm); // these are intended as bitmasks, even though there's just one now - enum { - AS_UV = 0x01, - AS_XYZ = 0x00 + enum class MakeAs : uint32_t { + UV = 0x01, + XYZ = 0x00 }; - void MakeTrimEdgesInto(SEdgeList *sel, int flags, SCurve *sc, STrimBy *stb); - void MakeEdgesInto(SShell *shell, SEdgeList *sel, int flags, - SShell *useCurvesFrom=NULL); + void MakeTrimEdgesInto(SEdgeList *sel, MakeAs flags, SCurve *sc, STrimBy *stb); + void MakeEdgesInto(SShell *shell, SEdgeList *sel, MakeAs flags, + SShell *useCurvesFrom=NULL); Vector ExactSurfaceTangentAt(Vector p, SSurface *srfA, SSurface *srfB, Vector dir); void MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList *sbl); void MakeClassifyingBsp(SShell *shell, SShell *useCurvesFrom); - double ChordToleranceForEdge(Vector a, Vector b); + double ChordToleranceForEdge(Vector a, Vector b) const; void MakeTriangulationGridInto(List *l, double vs, double vf, - bool swapped); - Vector PointAtMaybeSwapped(double u, double v, bool swapped); + bool swapped, int depth) const; + Vector PointAtMaybeSwapped(double u, double v, bool swapped) const; + Vector NormalAtMaybeSwapped(double u, double v, bool swapped) const; - void Reverse(void); - void Clear(void); + void Reverse(); + void Clear(); }; class SShell { @@ -365,58 +382,57 @@ public: void MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, RgbaColor color); + bool CheckNormalAxisRelationship(SBezierLoopSet *sbls, Vector pt, Vector axis, double da, double dx); void MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color, Group *group); - + void MakeFromHelicalRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, RgbaColor color, + Group *group, double angles, double anglef, double dists, double distf); + void MakeFirstOrderRevolvedSurfaces(Vector pt, Vector axis, int i0); void MakeFromUnionOf(SShell *a, SShell *b); void MakeFromDifferenceOf(SShell *a, SShell *b); - enum { - AS_UNION = 10, - AS_DIFFERENCE = 11, - AS_INTERSECT = 12 - }; - void MakeFromBoolean(SShell *a, SShell *b, int type); + void MakeFromIntersectionOf(SShell *a, SShell *b); + void MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type); void CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into); - void CopySurfacesTrimAgainst(SShell *sha, SShell *shb, SShell *into, - int type); + void CopySurfacesTrimAgainst(SShell *sha, SShell *shb, SShell *into, SSurface::CombineAs type); void MakeIntersectionCurvesAgainst(SShell *against, SShell *into); void MakeClassifyingBsps(SShell *useCurvesFrom); void AllPointsIntersecting(Vector a, Vector b, List *il, - bool seg, bool trimmed, bool inclTangent); + bool asSegment, bool trimmed, bool inclTangent); void MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal, SEdgeList *el, SShell *useCurvesFrom); void RewriteSurfaceHandlesForCurves(SShell *a, SShell *b); - void CleanupAfterBoolean(void); + void CleanupAfterBoolean(); // Definitions when classifying regions of a surface; it is either inside, // outside, or coincident (with parallel or antiparallel normal) with a // shell. - enum { + enum class Class : uint32_t { INSIDE = 100, OUTSIDE = 200, COINC_SAME = 300, COINC_OPP = 400 }; static const double DOTP_TOL; - int ClassifyRegion(Vector edge_n, Vector inter_surf_n, Vector edge_surf_n); - bool ClassifyEdge(int *indir, int *outdir, + Class ClassifyRegion(Vector edge_n, Vector inter_surf_n, + Vector edge_surf_n) const; + + bool ClassifyEdge(Class *indir, Class *outdir, Vector ea, Vector eb, - Vector p, - Vector edge_n_in, Vector edge_n_out, Vector surf_n); + Vector p, Vector edge_n_in, + Vector edge_n_out, Vector surf_n); void MakeFromCopyOf(SShell *a); void MakeFromTransformationOf(SShell *a, - Vector trans, Quaternion q, double scale); + Vector trans, Quaternion q, double scale); void MakeFromAssemblyOf(SShell *a, SShell *b); - void MergeCoincidentSurfaces(void); + void MergeCoincidentSurfaces(); void TriangulateInto(SMesh *sm); void MakeEdgesInto(SEdgeList *sel); - void MakeSectionEdgesInto(Vector n, double d, - SEdgeList *sel, SBezierList *sbl); - bool IsEmpty(void); + void MakeSectionEdgesInto(Vector n, double d, SEdgeList *sel, SBezierList *sbl); + bool IsEmpty() const; void RemapFaces(Group *g, int remap); - void Clear(void); + void Clear(); }; #endif diff --git a/src/srf/surfinter.cpp b/src/srf/surfinter.cpp index 79d71cd..0a82776 100644 --- a/src/srf/surfinter.cpp +++ b/src/srf/surfinter.cpp @@ -10,7 +10,7 @@ extern int FLAG; void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, - SShell *agnstA, SShell *agnstB, SShell *into) + SShell *agnstA, SShell *agnstB, SShell *into) { SCurve sc = {}; // Important to keep the order of (surfA, surfB) consistent; when we later @@ -27,19 +27,22 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, SBezier sbrev = *sb; sbrev.Reverse(); bool backwards = false; - for(se = into->curve.First(); se; se = into->curve.NextAfter(se)) { - if(se->isExact) { - if(sb->Equals(&(se->exact))) { - existing = se; - break; - } - if(sbrev.Equals(&(se->exact))) { - existing = se; - backwards = true; - break; +#pragma omp critical(into) + { + for(se = into->curve.First(); se; se = into->curve.NextAfter(se)) { + if(se->isExact) { + if(sb->Equals(&(se->exact))) { + existing = se; + break; + } + if(sbrev.Equals(&(se->exact))) { + existing = se; + backwards = true; + break; + } } } - } + }// end omp critical if(existing) { SCurvePt *v; for(v = existing->pts.First(); v; v = existing->pts.NextAfter(v)) { @@ -97,11 +100,14 @@ void SSurface::AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, } } #endif // 0 - // Nothing should be generating zero-len edges. - if((sb->Start()).Equals(sb->Finish())) oops(); + ssassert(!(sb->Start()).Equals(sb->Finish()), + "Unexpected zero-length edge"); - split.source = SCurve::FROM_INTERSECTION; - into->curve.AddAndAssignId(&split); + split.source = SCurve::Source::INTERSECTION; +#pragma omp critical(into) + { + into->curve.AddAndAssignId(&split); + } } void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, @@ -216,8 +222,8 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, p0 = n.ScaledBy(d).Plus(alu.ScaledBy(pm.Dot(alu))); List inters = {}; - sext->AllPointsIntersecting( - p0, p0.Plus(dp), &inters, false, false, true); + sext->AllPointsIntersecting(p0, p0.Plus(dp), &inters, + /*asSegment=*/false, /*trimmed=*/false, /*inclTangent=*/true); SInter *si; for(si = inters.First(); si; si = inters.NextAfter(si)) { @@ -277,20 +283,21 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, int i; for(i = 0; i < lv.n - 1; i++) { - Vector pa = lv.elem[i], pb = lv.elem[i+1]; + Vector pa = lv[i], pb = lv[i+1]; pa = pa.Minus(axis.ScaledBy(pa.Dot(axis))); pb = pb.Minus(axis.ScaledBy(pb.Dot(axis))); pa = pa.Plus(axisc); pb = pb.Plus(axisc); - b->AllPointsIntersecting(pa, pb, &inters, true, false, false); + b->AllPointsIntersecting(pa, pb, &inters, + /*asSegment=*/true,/*trimmed=*/false, /*inclTangent=*/false); } SInter *si; for(si = inters.First(); si; si = inters.NextAfter(si)) { Vector p = (si->p).Minus(axis.ScaledBy((si->p).Dot(axis))); double ub, vb; - b->ClosestPointTo(p, &ub, &vb, true); + b->ClosestPointTo(p, &ub, &vb, /*mustConverge=*/true); SSurface plane; plane = SSurface::FromPlane(p, axis.Normal(0), axis.Normal(1)); @@ -306,6 +313,45 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, inters.Clear(); lv.Clear(); } else { + if((degm == 1 && degn == 1) || (b->degm == 1 && b->degn == 1)) { + // we should only be here if just one surface is a plane because the + // plane-plane case was already handled above. Need to check the other + // nonplanar surface for trim curves that lie in the plane and are not + // already trimming both surfaces. This happens when we cut a Lathe shell + // on one of the seams for example. + // This also seems necessary to merge some coincident surfaces. + SSurface *splane, *sext; + SShell *shext; + if(degm == 1 && degn == 1) { // this and other checks assume coplanar ctrl pts. + splane = this; + sext = b; + shext = agnstB; + } else { + splane = b; + sext = this; + shext = agnstA; + } + bool foundExact = false; + SCurve *sc; + for(sc = shext->curve.First(); sc; sc = shext->curve.NextAfter(sc)) { + if(sc->source == SCurve::Source::INTERSECTION) continue; + if(!sc->isExact) continue; + if((sc->surfA != sext->h) && (sc->surfB != sext->h)) continue; + // we have a curve belonging to the curved surface and not the plane. + // does it lie completely in the plane? + if(splane->ContainsPlaneCurve(sc)) { + SBezier bezier = sc->exact; + AddExactIntersectionCurve(&bezier, b, agnstA, agnstB, into); + foundExact = true; + } + } + // if we found at lest one of these we don't want to do the numerical + // intersection as well. Sometimes it will also find the same curve but + // with different PWLs and the polygon will fail to assemble. + if(foundExact) + return; + } + // Try intersecting the surfaces numerically, by a marching algorithm. // First, we find all the intersections between a surface and the // boundary of the other surface. @@ -317,20 +363,21 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, *srfB = (a == 0) ? b : this; SEdgeList el = {}; - srfA->MakeEdgesInto(shA, &el, AS_XYZ, NULL); + srfA->MakeEdgesInto(shA, &el, MakeAs::XYZ, NULL); SEdge *se; for(se = el.l.First(); se; se = el.l.NextAfter(se)) { List lsi = {}; srfB->AllPointsIntersecting(se->a, se->b, &lsi, - true, true, false); - if(lsi.n == 0) continue; + /*asSegment=*/true, /*trimmed=*/true, /*inclTangent=*/false); + if(lsi.IsEmpty()) + continue; // Find the other surface that this curve trims. hSCurve hsc = { (uint32_t)se->auxA }; SCurve *sc = shA->curve.FindById(hsc); - hSSurface hother = (sc->surfA.v == srfA->h.v) ? + hSSurface hother = (sc->surfA == srfA->h) ? sc->surfB : sc->surfA; SSurface *other = shA->surface.FindById(hother); @@ -339,7 +386,11 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, Vector p = si->p; double u, v; srfB->ClosestPointTo(p, &u, &v); - srfB->PointOnSurfaces(srfA, other, &u, &v); + if(sc->isExact) { + srfB->PointOnCurve(&(sc->exact), &u, &v); + } else { + srfB->PointOnSurfaces(srfA, other, &u, &v); + } p = srfB->PointAt(u, v); if(!spl.ContainsPoint(p)) { SPoint sp; @@ -365,12 +416,12 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, sc.surfA = h; sc.surfB = b->h; sc.isExact = false; - sc.source = SCurve::FROM_INTERSECTION; + sc.source = SCurve::Source::INTERSECTION; - Vector start = spl.l.elem[0].p, - startv = spl.l.elem[0].auxv; + Vector start = spl.l[0].p, + startv = spl.l[0].auxv; spl.l.ClearTags(); - spl.l.elem[0].tag = 1; + spl.l[0].tag = 1; spl.l.RemoveTagged(); // Our chord tolerance is whatever the user specified @@ -450,7 +501,10 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, // And now we split and insert the curve SCurve split = sc.MakeCopySplitAgainst(agnstA, agnstB, this, b); sc.Clear(); - into->curve.AddAndAssignId(&split); +#pragma omp critical(into) + { + into->curve.AddAndAssignId(&split); + } } spl.Clear(); } @@ -460,7 +514,7 @@ void SSurface::IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, // Are two surfaces coincident, with the same (or with opposite) normals? // Currently handles planes only. //----------------------------------------------------------------------------- -bool SSurface::CoincidentWith(SSurface *ss, bool sameNormal) { +bool SSurface::CoincidentWith(SSurface *ss, bool sameNormal) const { if(degm != 1 || degn != 1) return false; if(ss->degm != 1 || ss->degn != 1) return false; @@ -480,7 +534,7 @@ bool SSurface::CoincidentWith(SSurface *ss, bool sameNormal) { return true; } -bool SSurface::CoincidentWithPlane(Vector n, double d) { +bool SSurface::CoincidentWithPlane(Vector n, double d) const { if(degm != 1 || degn != 1) return false; if(fabs(n.Dot(ctrl[0][0]) - d) > LENGTH_EPS) return false; if(fabs(n.Dot(ctrl[0][1]) - d) > LENGTH_EPS) return false; @@ -490,6 +544,24 @@ bool SSurface::CoincidentWithPlane(Vector n, double d) { return true; } +//----------------------------------------------------------------------------- +// Does a planar surface contain a curve? Does the curve lie completely in plane? +//----------------------------------------------------------------------------- +bool SSurface::ContainsPlaneCurve(SCurve *sc) const { + if(degm != 1 || degn != 1) return false; + if(!sc->isExact) return false; // we don't handle those (yet?) + + Vector p = ctrl[0][0]; + Vector n = NormalAt(0, 0).WithMagnitude(1); + double d = n.Dot(p); + + // check all control points on the curve + for(int i=0; i<= sc->exact.deg; i++) { + if(fabs(n.Dot(sc->exact.ctrl[i]) - d) > LENGTH_EPS) return false; + } + return true; +} + //----------------------------------------------------------------------------- // In our shell, find all surfaces that are coincident with the prototype // surface (with same or opposite normal, as specified), and copy all of @@ -502,7 +574,7 @@ void SShell::MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal, SSurface *ss; for(ss = surface.First(); ss; ss = surface.NextAfter(ss)) { if(proto->CoincidentWith(ss, sameNormal)) { - ss->MakeEdgesInto(this, el, SSurface::AS_XYZ, useCurvesFrom); + ss->MakeEdgesInto(this, el, SSurface::MakeAs::XYZ, useCurvesFrom); } } diff --git a/src/srf/triangulate.cpp b/src/srf/triangulate.cpp index 816eb3e..d02f963 100644 --- a/src/srf/triangulate.cpp +++ b/src/srf/triangulate.cpp @@ -36,7 +36,7 @@ void SPolygon::UvTriangulateInto(SMesh *m, SSurface *srf) { SContour merged = {}; top->tag = 1; top->CopyInto(&merged); - (merged.l.n)--; + merged.l.RemoveLast(1); // List all of the edges, for testing whether bridges work. SEdgeList el = {}; @@ -109,12 +109,13 @@ bool SContour::BridgeToContour(SContour *sc, SEdgeList *avoidEdges, List *avoidPts) { int i, j; + bool withbridge = true; // Start looking for a bridge on our new hole near its leftmost (min x) // point. int sco = 0; for(i = 0; i < (sc->l.n - 1); i++) { - if((sc->l.elem[i].p).EqualsExactly(sc->xminPt)) { + if((sc->l[i].p).EqualsExactly(sc->xminPt)) { sco = i; } } @@ -123,8 +124,8 @@ bool SContour::BridgeToContour(SContour *sc, // to the leftmost point of the new segment. int thiso = 0; double dmin = 1e10; - for(i = 0; i < l.n; i++) { - Vector p = l.elem[i].p; + for(i = 0; i < l.n-1; i++) { + Vector p = l[i].p; double d = (p.Minus(sc->xminPt)).MagSquared(); if(d < dmin) { dmin = d; @@ -139,8 +140,8 @@ bool SContour::BridgeToContour(SContour *sc, // First check if the contours share a point; in that case we should // merge them there, without a bridge. for(i = 0; i < l.n; i++) { - thisp = WRAP(i+thiso, l.n); - a = l.elem[thisp].p; + thisp = WRAP(i+thiso, l.n-1); + a = l[thisp].p; for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) { if(f->Equals(a)) break; @@ -149,9 +150,10 @@ bool SContour::BridgeToContour(SContour *sc, for(j = 0; j < (sc->l.n - 1); j++) { scp = WRAP(j+sco, (sc->l.n - 1)); - b = sc->l.elem[scp].p; + b = sc->l[scp].p; if(a.Equals(b)) { + withbridge = false; goto haveEdge; } } @@ -160,7 +162,7 @@ bool SContour::BridgeToContour(SContour *sc, // If that fails, look for a bridge that does not intersect any edges. for(i = 0; i < l.n; i++) { thisp = WRAP(i+thiso, l.n); - a = l.elem[thisp].p; + a = l[thisp].p; for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) { if(f->Equals(a)) break; @@ -169,7 +171,7 @@ bool SContour::BridgeToContour(SContour *sc, for(j = 0; j < (sc->l.n - 1); j++) { scp = WRAP(j+sco, (sc->l.n - 1)); - b = sc->l.elem[scp].p; + b = sc->l[scp].p; for(f = avoidPts->First(); f; f = avoidPts->NextAfter(f)) { if(f->Equals(b)) break; @@ -190,22 +192,28 @@ bool SContour::BridgeToContour(SContour *sc, haveEdge: SContour merged = {}; for(i = 0; i < l.n; i++) { - merged.AddPoint(l.elem[i].p); + if(withbridge || (i != thisp)) { + merged.AddPoint(l[i].p); + } if(i == thisp) { // less than or equal; need to duplicate the join point for(j = 0; j <= (sc->l.n - 1); j++) { int jp = WRAP(j + scp, (sc->l.n - 1)); - merged.AddPoint((sc->l.elem[jp]).p); + merged.AddPoint((sc->l[jp]).p); } // and likewise duplicate join point for the outer curve - merged.AddPoint(l.elem[i].p); + if(withbridge) { + merged.AddPoint(l[i].p); + } } } // and future bridges mustn't cross our bridge, and it's tricky to get // things right if two bridges come from the same point - avoidEdges->AddEdge(a, b); - avoidPts->Add(&a); + if(withbridge) { + avoidEdges->AddEdge(a, b); + avoidPts->Add(&a); + } avoidPts->Add(&b); l.Clear(); @@ -213,14 +221,71 @@ haveEdge: return true; } -bool SContour::IsEar(int bp, double scaledEps) { +bool SContour::IsEmptyTriangle(int ap, int bp, int cp, double scaledEPS) const { + + STriangle tr = {}; + tr.a = l[ap].p; + tr.b = l[bp].p; + tr.c = l[cp].p; + + // Accelerate with an axis-aligned bounding box test + Vector maxv = tr.a, minv = tr.a; + (tr.b).MakeMaxMin(&maxv, &minv); + (tr.c).MakeMaxMin(&maxv, &minv); + + Vector n = Vector::From(0, 0, -1); + + int i; + for(i = 0; i < l.n; i++) { + if(i == ap || i == bp || i == cp) continue; + + Vector p = l[i].p; + if(p.OutsideAndNotOn(maxv, minv)) continue; + + // A point on the edge of the triangle is considered to be inside, + // and therefore makes it a non-ear; but a point on the vertex is + // "outside", since that's necessary to make bridges work. + if(p.EqualsExactly(tr.a)) continue; + if(p.EqualsExactly(tr.b)) continue; + if(p.EqualsExactly(tr.c)) continue; + + if(tr.ContainsPointProjd(n, p)) { + return false; + } + } + return true; +} + +// Test if ray b->d passes through triangle a,b,c +static bool RayIsInside(Vector a, Vector c, Vector b, Vector d) { + // coincident edges are not considered to intersect the triangle + if (d.Equals(a)) return false; + if (d.Equals(c)) return false; + // if d and c are on opposite sides of ba, we are ok + // likewise if d and a are on opposite sides of bc + Vector ba = a.Minus(b); + Vector bc = c.Minus(b); + Vector bd = d.Minus(b); + + // perpendicular to (x,y) is (x,-y) so dot that with the two points. If they + // have opposite signs their product will be negative. If bd and bc are on + // opposite sides of ba the ray does not intersect. Likewise for bd,ba and bc. + if ( (bd.x*(ba.y) + (bd.y * (-ba.x))) * ( bc.x*(ba.y) + (bc.y * (-ba.x))) < LENGTH_EPS) + return false; + if ( (bd.x*(bc.y) + (bd.y * (-bc.x))) * ( ba.x*(bc.y) + (ba.y * (-bc.x))) < LENGTH_EPS) + return false; + + return true; +} + +bool SContour::IsEar(int bp, double scaledEps) const { int ap = WRAP(bp-1, l.n), cp = WRAP(bp+1, l.n); STriangle tr = {}; - tr.a = l.elem[ap].p; - tr.b = l.elem[bp].p; - tr.c = l.elem[cp].p; + tr.a = l[ap].p; + tr.b = l[bp].p; + tr.c = l[cp].p; if((tr.a).Equals(tr.c)) { // This is two coincident and anti-parallel edges. Zero-area, so @@ -244,15 +309,28 @@ bool SContour::IsEar(int bp, double scaledEps) { for(i = 0; i < l.n; i++) { if(i == ap || i == bp || i == cp) continue; - Vector p = l.elem[i].p; + Vector p = l[i].p; if(p.OutsideAndNotOn(maxv, minv)) continue; // A point on the edge of the triangle is considered to be inside, // and therefore makes it a non-ear; but a point on the vertex is // "outside", since that's necessary to make bridges work. if(p.EqualsExactly(tr.a)) continue; - if(p.EqualsExactly(tr.b)) continue; if(p.EqualsExactly(tr.c)) continue; + // points coincident with bp have to be allowed for bridges but edges + // from that other point must not cross through our triangle. + if(p.EqualsExactly(tr.b)) { + int j = WRAP(i-1, l.n); + int k = WRAP(i+1, l.n); + Vector jp = l[j].p; + Vector kp = l[k].p; + + // two consecutive bridges (A,B,C) and later (C,B,A) are not an ear + if (jp.Equals(tr.c) && kp.Equals(tr.a)) return false; + // check both edges from the point in question + if (!RayIsInside(tr.a, tr.c, p,jp) && !RayIsInside(tr.a, tr.c, p,kp)) + continue; + } if(tr.ContainsPointProjd(n, p)) { return false; @@ -266,9 +344,9 @@ void SContour::ClipEarInto(SMesh *m, int bp, double scaledEps) { cp = WRAP(bp+1, l.n); STriangle tr = {}; - tr.a = l.elem[ap].p; - tr.b = l.elem[bp].p; - tr.c = l.elem[cp].p; + tr.a = l[ap].p; + tr.b = l[bp].p; + tr.c = l[cp].p; if(tr.Normal().MagSquared() < scaledEps*scaledEps) { // A vertex with more than two edges will cause us to generate // zero-area triangles, which must be culled. @@ -278,11 +356,11 @@ void SContour::ClipEarInto(SMesh *m, int bp, double scaledEps) { // By deleting the point at bp, we may change the ear-ness of the points // on either side. - l.elem[ap].ear = SPoint::UNKNOWN; - l.elem[cp].ear = SPoint::UNKNOWN; + l[ap].ear = EarType::UNKNOWN; + l[cp].ear = EarType::UNKNOWN; l.ClearTags(); - l.elem[bp].tag = 1; + l[bp].tag = 1; l.RemoveTagged(); } @@ -297,29 +375,87 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) { int i; // Clean the original contour by removing any zero-length edges. + // initialize eartypes to unknown while we're going over them. l.ClearTags(); + l[0].ear = EarType::UNKNOWN; for(i = 1; i < l.n; i++) { - if((l.elem[i].p).Equals(l.elem[i-1].p)) { - l.elem[i].tag = 1; + l[i].ear = EarType::UNKNOWN; + if((l[i].p).Equals(l[i-1].p)) { + l[i].tag = 1; } } - l.RemoveTagged(); - - // Now calculate the ear-ness of each vertex - for(i = 0; i < l.n; i++) { - (l.elem[i]).ear = IsEar(i, scaledEps) ? SPoint::EAR : SPoint::NOT_EAR; + if( (l[0].p).Equals(l[l.n-1].p) ) { + l[l.n-1].tag = 1; } + l.RemoveTagged(); - bool toggle = false; - while(l.n > 3) { - // Some points may have changed ear-ness, so recalculate - for(i = 0; i < l.n; i++) { - if(l.elem[i].ear == SPoint::UNKNOWN) { - (l.elem[i]).ear = IsEar(i, scaledEps) ? - SPoint::EAR : SPoint::NOT_EAR; + // Handle simple triangle fans all at once. This pass is optional. + if(srf->degm == 1 && srf->degn == 1) { + l.ClearTags(); + int j=0; + int pstart = 0; + double elen = -1.0; + double oldspan = 0.0; + for(i = 1; i < l.n; i++) { + Vector ab = l[i].p.Minus(l[i-1].p); + // first time just measure the segment + if (elen < 0.0) { + elen = ab.Dot(ab); + oldspan = elen; + j = 1; + continue; + } + // check for consecutive segments of similar size which are also + // ears and where the group forms a convex ear + bool end = false; + double ratio = ab.Dot(ab) / elen; + if ((ratio < 0.25) || (ratio > 4.0)) end = true; + + double slen = l[pstart].p.Minus(l[i].p).MagSquared(); + if (slen < oldspan) end = true; + + if (!IsEar(i-1, scaledEps) ) end = true; +// if ((j>0) && !IsEar(pstart, i-1, i, scaledEps)) end = true; + if ((j>0) && !IsEmptyTriangle(pstart, i-1, i, scaledEps)) end = true; + // the new segment is valid so add to the fan + if (!end) { + j++; + oldspan = slen; + } + // we need to stop at the end of polygon but may still + if (i == l.n-1) { + end = true; + } + if (end) { // triangulate the fan and tag the verticies + if (j > 3) { + Vector center = l[pstart+1].p.Plus(l[pstart+j-1].p).ScaledBy(0.5); + for (int x=0; xAddTriangle(&tr); + } + for (int x=1; xAddTriangle(&tr); + } + pstart = i-1; + elen = ab.Dot(ab); + oldspan = elen; + j = 1; } } + l.RemoveTagged(); + } // end optional fan creation pass + bool toggle = false; + while(l.n > 3) { int bestEar = -1; double bestChordTol = VERY_POSITIVE; // Alternate the starting position so we generate strip-like @@ -328,7 +464,10 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) { int offset = toggle ? -1 : 0; for(i = 0; i < l.n; i++) { int ear = WRAP(i+offset, l.n); - if(l.elem[ear].ear == SPoint::EAR) { + if(l[ear].ear == EarType::UNKNOWN) { + (l[ear]).ear = IsEar(ear, scaledEps) ? EarType::EAR : EarType::NOT_EAR; + } + if(l[ear].ear == EarType::EAR) { if(srf->degm == 1 && srf->degn == 1) { // This is a plane; any ear is a good ear. bestEar = ear; @@ -337,8 +476,8 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) { // If we are triangulating a curved surface, then try to // clip ears that have a small chord tolerance from the // surface. - Vector prev = l.elem[WRAP((i+offset-1), l.n)].p, - next = l.elem[WRAP((i+offset+1), l.n)].p; + Vector prev = l[WRAP((i+offset-1), l.n)].p, + next = l[WRAP((i+offset+1), l.n)].p; double tol = srf->ChordToleranceForEdge(prev, next); if(tol < bestChordTol - scaledEps) { bestEar = ear; @@ -359,7 +498,7 @@ void SContour::UvTriangulateInto(SMesh *m, SSurface *srf) { ClipEarInto(m, 0, scaledEps); // add the last triangle } -double SSurface::ChordToleranceForEdge(Vector a, Vector b) { +double SSurface::ChordToleranceForEdge(Vector a, Vector b) const { Vector as = PointAt(a.x, a.y), bs = PointAt(b.x, b.y); double worst = VERY_NEGATIVE; @@ -374,7 +513,7 @@ double SSurface::ChordToleranceForEdge(Vector a, Vector b) { return sqrt(worst); } -Vector SSurface::PointAtMaybeSwapped(double u, double v, bool swapped) { +Vector SSurface::PointAtMaybeSwapped(double u, double v, bool swapped) const { if(swapped) { return PointAt(v, u); } else { @@ -382,13 +521,24 @@ Vector SSurface::PointAtMaybeSwapped(double u, double v, bool swapped) { } } +Vector SSurface::NormalAtMaybeSwapped(double u, double v, bool swapped) const { + Vector du, dv; + if(swapped) { + TangentsAt(v, u, &dv, &du); + } else { + TangentsAt(u, v, &du, &dv); + } + return du.Cross(dv).WithMagnitude(1.0); +} + void SSurface::MakeTriangulationGridInto(List *l, double vs, double vf, - bool swapped) + bool swapped, int depth) const { double worst = 0; // Try piecewise linearizing four curves, at u = 0, 1/3, 2/3, 1; choose // the worst chord tolerance of any of those. + double worst_twist = 1.0; int i; for(i = 0; i <= 3; i++) { double u = i/3.0; @@ -405,16 +555,24 @@ void SSurface::MakeTriangulationGridInto(List *l, double vs, double vf, Vector pm1 = PointAtMaybeSwapped(u, vm1, swapped), pm2 = PointAtMaybeSwapped(u, vm2, swapped); + // 0.999 is about 2.5 degrees of twist over the middle 1/3 V-span. + // we don't check at the ends because the derivative may not be valid there. + double twist = 1.0; + if (degm == 1) twist = NormalAtMaybeSwapped(u, vm1, swapped).Dot( + NormalAtMaybeSwapped(u, vm2, swapped) ); + if (twist < worst_twist) worst_twist = twist; + worst = max(worst, pm1.DistanceToLine(ps, pf.Minus(ps))); worst = max(worst, pm2.DistanceToLine(ps, pf.Minus(ps))); } double step = 1.0/SS.GetMaxSegments(); - if((vf - vs) < step || worst < SS.ChordTolMm()) { + if( ((vf - vs) < step || worst < SS.ChordTolMm()) + && ((worst_twist > 0.999) || (depth > 3)) ) { l->Add(&vf); } else { - MakeTriangulationGridInto(l, vs, (vs+vf)/2, swapped); - MakeTriangulationGridInto(l, (vs+vf)/2, vf, swapped); + MakeTriangulationGridInto(l, vs, (vs+vf)/2, swapped, depth+1); + MakeTriangulationGridInto(l, (vs+vf)/2, vf, swapped, depth+1); } } @@ -432,74 +590,145 @@ void SPolygon::UvGridTriangulateInto(SMesh *mesh, SSurface *srf) { List li, lj; li = {}; lj = {}; - double v = 0; - li.Add(&v); - srf->MakeTriangulationGridInto(&li, 0, 1, true); - lj.Add(&v); - srf->MakeTriangulationGridInto(&lj, 0, 1, false); - - // Now iterate over each quad in the grid. If it's outside the polygon, - // or if it intersects the polygon, then we discard it. Otherwise we - // generate two triangles in the mesh, and cut it out of our polygon. - int i, j; - for(i = 0; i < (li.n - 1); i++) { - for(j = 0; j < (lj.n - 1); j++) { - double us = li.elem[i], uf = li.elem[i+1], - vs = lj.elem[j], vf = lj.elem[j+1]; - - Vector a = Vector::From(us, vs, 0), - b = Vector::From(us, vf, 0), - c = Vector::From(uf, vf, 0), - d = Vector::From(uf, vs, 0); - - if(orig.AnyEdgeCrossings(a, b, NULL) || - orig.AnyEdgeCrossings(b, c, NULL) || - orig.AnyEdgeCrossings(c, d, NULL) || - orig.AnyEdgeCrossings(d, a, NULL)) - { - continue; - } + double v[5] = {0.0, 0.25, 0.5, 0.75, 1.0}; + li.Add(&v[0]); + srf->MakeTriangulationGridInto(&li, 0, 1, /*swapped=*/true, 0); + lj.Add(&v[0]); + srf->MakeTriangulationGridInto(&lj, 0, 1, /*swapped=*/false, 0); + + // force 2nd order grid to have at least 4 segments in each direction + if ((li.n < 5) && (srf->degm>1)) { // 4 segments minimun + li.Clear(); + li.Add(&v[0]);li.Add(&v[1]);li.Add(&v[2]);li.Add(&v[3]);li.Add(&v[4]); + } + if ((lj.n < 5) && (srf->degn>1)) { // 4 segments minimun + lj.Clear(); + lj.Add(&v[0]);lj.Add(&v[1]);lj.Add(&v[2]);lj.Add(&v[3]);lj.Add(&v[4]); + } - // There's no intersections, so it doesn't matter which point - // we decide to test. - if(!this->ContainsPoint(a)) { - continue; - } + if ((li.n > 3) && (lj.n > 3)) { + // Now iterate over each quad in the grid. If it's outside the polygon, + // or if it intersects the polygon, then we discard it. Otherwise we + // generate two triangles in the mesh, and cut it out of our polygon. + // Quads around the perimeter would be rejected by AnyEdgeCrossings. + std::vector bottom(lj.n, false); // did we use this quad? + Vector tu = {0,0,0}, tv = {0,0,0}; + int i, j; + for(i = 1; i < (li.n-1); i++) { + bool prev_flag = false; + for(j = 1; j < (lj.n-1); j++) { + bool this_flag = true; + double us = li[i], uf = li[i+1], + vs = lj[j], vf = lj[j+1]; + + Vector a = Vector::From(us, vs, 0), + b = Vector::From(us, vf, 0), + c = Vector::From(uf, vf, 0), + d = Vector::From(uf, vs, 0); + + // | d-----c + // | | | + // | | | + // | a-----b + // | + // +-------------> j/v axis + + if( (i==(li.n-2)) || (j==(lj.n-2)) || + orig.AnyEdgeCrossings(a, b, NULL) || + orig.AnyEdgeCrossings(b, c, NULL) || + orig.AnyEdgeCrossings(c, d, NULL) || + orig.AnyEdgeCrossings(d, a, NULL)) + { + this_flag = false; + } - // Add the quad to our mesh - STriangle tr = {}; - tr.a = a; - tr.b = b; - tr.c = c; - mesh->AddTriangle(&tr); - tr.a = a; - tr.b = c; - tr.c = d; - mesh->AddTriangle(&tr); - - holes.AddEdge(a, b); - holes.AddEdge(b, c); - holes.AddEdge(c, d); - holes.AddEdge(d, a); + // There's no intersections, so it doesn't matter which point + // we decide to test. + if(!this->ContainsPoint(a)) { + this_flag = false; + } + + if (this_flag) { + // Add the quad to our mesh + srf->TangentsAt(us,vs, &tu,&tv); + if(tu.Dot(tv) < LENGTH_EPS) { + /* Split "the other way" if angle>90 + compare to LENGTH_EPS instead of zero to avoid alternating triangle + "orientations" when the tangents are orthogonal (revolve, lathe etc.) + this results in a higher quality mesh. */ + STriangle tr = {}; + tr.a = a; + tr.b = b; + tr.c = c; + mesh->AddTriangle(&tr); + tr.a = a; + tr.b = c; + tr.c = d; + mesh->AddTriangle(&tr); + } else{ + STriangle tr = {}; + tr.a = a; + tr.b = b; + tr.c = d; + mesh->AddTriangle(&tr); + tr.a = b; + tr.b = c; + tr.c = d; + mesh->AddTriangle(&tr); + } + if (!prev_flag) // add our own left edge + holes.AddEdge(d, a); + if (!bottom[j]) // add our own bottom edge + holes.AddEdge(a, b); + } else { + if (prev_flag) // add our left neighbots right edge + holes.AddEdge(a, d); + if (bottom[j]) // add our bottom neighbors top edge + holes.AddEdge(b, a); + } + prev_flag = this_flag; + bottom[j] = this_flag; + } } - } - holes.CullExtraneousEdges(); - SPolygon hp = {}; - holes.AssemblePolygon(&hp, NULL, true); + // Because no duplicate edges were created we do not need to cull them. + SPolygon hp = {}; + holes.AssemblePolygon(&hp, NULL, /*keepDir=*/true); - SContour *sc; - for(sc = hp.l.First(); sc; sc = hp.l.NextAfter(sc)) { - l.Add(sc); + SContour *sc; + for(sc = hp.l.First(); sc; sc = hp.l.NextAfter(sc)) { + l.Add(sc); + } + hp.l.Clear(); } - orig.Clear(); holes.Clear(); li.Clear(); lj.Clear(); - hp.l.Clear(); - UvTriangulateInto(mesh, srf); } +void SPolygon::TriangulateInto(SMesh *m) const { + Vector n = normal; + if(n.Equals(Vector::From(0.0, 0.0, 0.0))) { + n = ComputeNormal(); + } + Vector u = n.Normal(0); + Vector v = n.Normal(1); + + SPolygon p = {}; + this->InverseTransformInto(&p, u, v, n); + + SSurface srf = SSurface::FromPlane(Vector::From(0.0, 0.0, 0.0), + Vector::From(1.0, 0.0, 0.0), + Vector::From(0.0, 1.0, 0.0)); + SMesh pm = {}; + p.UvTriangulateInto(&pm, &srf); + for(STriangle st : pm.l) { + st = st.Transform(u, v, n); + m->AddTriangle(&st); + } + p.Clear(); + pm.Clear(); +} diff --git a/src/style.cpp b/src/style.cpp index 4d437cd..1f719f6 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -6,9 +6,6 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" -#include - -#define DEFAULT_TEXT_HEIGHT 11.5 const Style::Default Style::Defaults[] = { { { ACTIVE_GRP }, "ActiveGrp", RGBf(1.0, 1.0, 1.0), 1.5, 4 }, @@ -21,10 +18,10 @@ const Style::Default Style::Defaults[] = { { { HOVERED }, "Hovered", RGBf(1.0, 1.0, 0.0), 1.5, 0 }, { { CONTOUR_FILL }, "ContourFill", RGBf(0.0, 0.1, 0.1), 1.0, 0 }, { { NORMALS }, "Normals", RGBf(0.0, 0.4, 0.4), 1.0, 0 }, - { { ANALYZE }, "Analyze", RGBf(0.0, 1.0, 1.0), 1.0, 0 }, + { { ANALYZE }, "Analyze", RGBf(0.0, 1.0, 1.0), 3.0, 0 }, { { DRAW_ERROR }, "DrawError", RGBf(1.0, 0.0, 0.0), 8.0, 0 }, { { DIM_SOLID }, "DimSolid", RGBf(0.1, 0.1, 0.1), 1.0, 0 }, - { { HIDDEN_EDGE }, "HiddenEdge", RGBf(0.8, 0.8, 0.8), 2.0, 1 }, + { { HIDDEN_EDGE }, "HiddenEdge", RGBf(0.8, 0.8, 0.8), 1.0, 1 }, { { OUTLINE }, "Outline", RGBf(0.8, 0.8, 0.8), 3.0, 5 }, { { 0 }, NULL, RGBf(0.0, 0.0, 0.0), 0.0, 0 } }; @@ -51,7 +48,7 @@ std::string Style::CnfPrefixToName(const std::string &prefix) { return name; } -void Style::CreateAllDefaultStyles(void) { +void Style::CreateAllDefaultStyles() { const Default *d; for(d = &(Defaults[0]); d->h.v; d++) { (void)Get(d->h); @@ -62,7 +59,7 @@ void Style::CreateDefaultStyle(hStyle h) { bool isDefaultStyle = true; const Default *d; for(d = &(Defaults[0]); d->h.v; d++) { - if(d->h.v == h.v) break; + if(d->h == h) break; } if(!d->h.v) { // Not a default style; so just create it the same as our default @@ -84,42 +81,46 @@ void Style::CreateDefaultStyle(hStyle h) { } void Style::FillDefaultStyle(Style *s, const Default *d, bool factory) { + Platform::SettingsRef settings = Platform::GetSettings(); + if(d == NULL) d = &Defaults[0]; - s->color = (factory) ? d->color : CnfThawColor(d->color, CnfColor(d->cnfPrefix)); - s->width = (factory) ? d->width : CnfThawFloat((float)(d->width), CnfWidth(d->cnfPrefix)); - s->widthAs = UNITS_AS_PIXELS; - s->textHeight = (factory) ? DEFAULT_TEXT_HEIGHT - : CnfThawFloat(DEFAULT_TEXT_HEIGHT, CnfTextHeight(d->cnfPrefix)); - s->textHeightAs = UNITS_AS_PIXELS; - s->textOrigin = 0; + s->color = (factory) + ? d->color + : settings->ThawColor(CnfColor(d->cnfPrefix), d->color); + s->width = (factory) + ? d->width + : settings->ThawFloat(CnfWidth(d->cnfPrefix), (float)(d->width)); + s->widthAs = UnitsAs::PIXELS; + s->textHeight = (factory) ? 11.5 + : settings->ThawFloat(CnfTextHeight(d->cnfPrefix), 11.5); + s->textHeightAs = UnitsAs::PIXELS; + s->textOrigin = TextOrigin::NONE; s->textAngle = 0; s->visible = true; s->exportable = true; s->filled = false; s->fillColor = RGBf(0.3, 0.3, 0.3); - s->stippleType = (d->h.v == Style::HIDDEN_EDGE) ? Style::STIPPLE_DASH - : Style::STIPPLE_CONTINUOUS; + s->stippleType = (d->h.v == Style::HIDDEN_EDGE) ? StipplePattern::DASH + : StipplePattern::CONTINUOUS; s->stippleScale = 15.0; s->zIndex = d->zIndex; } -void Style::LoadFactoryDefaults(void) { +void Style::LoadFactoryDefaults() { const Default *d; for(d = &(Defaults[0]); d->h.v; d++) { Style *s = Get(d->h); FillDefaultStyle(s, d, /*factory=*/true); } SS.backgroundColor = RGBi(0, 0, 0); - if(SS.bgImage.fromFile) MemFree(SS.bgImage.fromFile); - SS.bgImage.fromFile = NULL; } -void Style::FreezeDefaultStyles(void) { +void Style::FreezeDefaultStyles(Platform::SettingsRef settings) { const Default *d; for(d = &(Defaults[0]); d->h.v; d++) { - CnfFreezeColor(Color(d->h), CnfColor(d->cnfPrefix)); - CnfFreezeFloat((float)Width(d->h), CnfWidth(d->cnfPrefix)); - CnfFreezeFloat((float)TextHeight(d->h), CnfTextHeight(d->cnfPrefix)); + settings->FreezeColor(CnfColor(d->cnfPrefix), Color(d->h)); + settings->FreezeFloat(CnfWidth(d->cnfPrefix), (float)Width(d->h)); + settings->FreezeFloat(CnfTextHeight(d->cnfPrefix), (float)TextHeight(d->h)); } } @@ -158,19 +159,19 @@ void Style::AssignSelectionToStyle(uint32_t v) { if(!c->IsStylable()) continue; c->disp.style.v = v; + SS.MarkGroupDirty(c->group); } if(showError) { - Error("Can't assign style to an entity that's derived from another " - "entity; try assigning a style to this entity's parent."); + Error(_("Can't assign style to an entity that's derived from another " + "entity; try assigning a style to this entity's parent.")); } SS.GW.ClearSelection(); - InvalidateGraphics(); - SS.ScheduleGenerateAll(); + SS.GW.Invalidate(); // And show that style's info screen in the text window. - SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO); + SS.TW.GoToScreen(TextWindow::Screen::STYLE_INFO); SS.TW.shown.style.v = v; SS.ScheduleShowTW(); } @@ -201,7 +202,7 @@ RgbaColor Style::Color(int s, bool forExport) { hStyle hs = { (uint32_t)s }; return Color(hs, forExport); } -float Style::Width(int s) { +double Style::Width(int s) { hStyle hs = { (uint32_t)s }; return Width(hs); } @@ -251,16 +252,13 @@ RgbaColor Style::FillColor(hStyle h, bool forExport) { //----------------------------------------------------------------------------- // Return the width associated with our style in pixels.. //----------------------------------------------------------------------------- -float Style::Width(hStyle h) { - double r = 1.0; +double Style::Width(hStyle h) { Style *s = Get(h); - if(s->widthAs == UNITS_AS_MM) { - r = s->width * SS.GW.scale; - } else if(s->widthAs == UNITS_AS_PIXELS) { - r = s->width; + switch(s->widthAs) { + case UnitsAs::MM: return s->width * SS.GW.scale; + case UnitsAs::PIXELS: return s->width; } - // This returns a float because ssglLineWidth expects a float, avoid casts. - return (float)r; + ssassert(false, "Unexpected units"); } //----------------------------------------------------------------------------- @@ -274,13 +272,13 @@ double Style::WidthMm(int hs) { //----------------------------------------------------------------------------- // Return the associated text height, in pixels. //----------------------------------------------------------------------------- -double Style::TextHeight(hStyle hs) { - Style *s = Get(hs); - if(s->textHeightAs == UNITS_AS_MM) { - return s->textHeight * SS.GW.scale; - } else /* s->textHeightAs == UNITS_AS_PIXELS */ { - return s->textHeight; +double Style::TextHeight(hStyle h) { + Style *s = Get(h); + switch(s->textHeightAs) { + case UnitsAs::MM: return s->textHeight * SS.GW.scale; + case UnitsAs::PIXELS: return s->textHeight; } + ssassert(false, "Unexpected units"); } double Style::DefaultTextHeight() { @@ -288,6 +286,32 @@ double Style::DefaultTextHeight() { return TextHeight(hs); } +//----------------------------------------------------------------------------- +// Return the parameters of this style, as a canvas stroke. +//----------------------------------------------------------------------------- +Canvas::Stroke Style::Stroke(hStyle hs) { + Canvas::Stroke stroke = {}; + Style *style = Style::Get(hs); + stroke.color = style->color; + stroke.stipplePattern = style->stippleType; + stroke.stippleScale = style->stippleScale; + stroke.width = style->width; + switch(style->widthAs) { + case Style::UnitsAs::PIXELS: + stroke.unit = Canvas::Unit::PX; + break; + case Style::UnitsAs::MM: + stroke.unit = Canvas::Unit::MM; + break; + } + return stroke; +} + +Canvas::Stroke Style::Stroke(int hsv) { + hStyle hs = { (uint32_t) hsv }; + return Style::Stroke(hs); +} + //----------------------------------------------------------------------------- // Should lines and curves from this style appear in the output file? Only // if it's both shown and exportable. @@ -313,7 +337,7 @@ hStyle Style::ForEntity(hEntity he) { // Otherwise, we use the default rules. hStyle hs; - if(e->group.v != SS.GW.activeGroup.v) { + if(e->group != SS.GW.activeGroup) { hs.v = INACTIVE_GRP; } else if(e->construction) { hs.v = CONSTRUCTION; @@ -323,22 +347,22 @@ hStyle Style::ForEntity(hEntity he) { return hs; } -int Style::PatternType(hStyle hs) { +StipplePattern Style::PatternType(hStyle hs) { Style *s = Get(hs); return s->stippleType; } double Style::StippleScaleMm(hStyle hs) { Style *s = Get(hs); - if(s->widthAs == UNITS_AS_MM) { + if(s->widthAs == UnitsAs::MM) { return s->stippleScale; - } else if(s->widthAs == UNITS_AS_PIXELS) { + } else if(s->widthAs == UnitsAs::PIXELS) { return s->stippleScale / SS.GW.scale; } return 1.0; } -std::string Style::DescriptionString(void) { +std::string Style::DescriptionString() const { if(name.empty()) { return ssprintf("s%03x-(unnamed)", h.v); } else { @@ -348,16 +372,17 @@ std::string Style::DescriptionString(void) { void TextWindow::ScreenShowListOfStyles(int link, uint32_t v) { - SS.TW.GoToScreen(SCREEN_LIST_OF_STYLES); + SS.TW.GoToScreen(Screen::LIST_OF_STYLES); } void TextWindow::ScreenShowStyleInfo(int link, uint32_t v) { - SS.TW.GoToScreen(SCREEN_STYLE_INFO); + GraphicsWindow::MenuEdit(Command::UNSELECT_ALL); + SS.TW.GoToScreen(Screen::STYLE_INFO); SS.TW.shown.style.v = v; } void TextWindow::ScreenLoadFactoryDefaultStyles(int link, uint32_t v) { Style::LoadFactoryDefaults(); - SS.TW.GoToScreen(SCREEN_LIST_OF_STYLES); + SS.TW.GoToScreen(Screen::LIST_OF_STYLES); } void TextWindow::ScreenCreateCustomStyle(int link, uint32_t v) { @@ -367,88 +392,10 @@ void TextWindow::ScreenCreateCustomStyle(int link, uint32_t v) { void TextWindow::ScreenChangeBackgroundColor(int link, uint32_t v) { RgbaColor rgb = SS.backgroundColor; SS.TW.ShowEditControlWithColorPicker(3, rgb); - SS.TW.edit.meaning = EDIT_BACKGROUND_COLOR; + SS.TW.edit.meaning = Edit::BACKGROUND_COLOR; } -static int RoundUpToPowerOfTwo(int v) -{ - int i; - for(i = 0; i < 31; i++) { - int vt = (1 << i); - if(vt >= v) { - return vt; - } - } - return 0; -} - -void TextWindow::ScreenBackgroundImage(int link, uint32_t v) { - if(SS.bgImage.fromFile) MemFree(SS.bgImage.fromFile); - SS.bgImage.fromFile = NULL; - - if(link == 'l') { - FILE *f = NULL; - png_struct *png_ptr = NULL; - png_info *info_ptr = NULL; - - std::string importFile; - if(!GetOpenFile(&importFile, "", PngFileFilter)) goto err; - f = ssfopen(importFile, "rb"); - if(!f) goto err; - - uint8_t header[8]; - if (fread(header, 1, 8, f) != 8) - goto err; - if(png_sig_cmp(header, 0, 8)) goto err; - - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, - NULL, NULL, NULL); - if(!png_ptr) goto err; - - info_ptr = png_create_info_struct(png_ptr); - if(!info_ptr) goto err; - - if(setjmp(png_jmpbuf(png_ptr))) goto err; - - png_init_io(png_ptr, f); - png_set_sig_bytes(png_ptr, 8); - - png_read_png(png_ptr, info_ptr, - PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_ALPHA, NULL); - - int w; w = (int)png_get_image_width(png_ptr, info_ptr); - int h; h = (int)png_get_image_height(png_ptr, info_ptr); - uint8_t **rows; rows = png_get_rows(png_ptr, info_ptr); - - // Round to next-highest powers of two, since the textures require - // that. And round up to 4, to guarantee 32-bit alignment. - int rw; rw = max(4, RoundUpToPowerOfTwo(w)); - int rh; rh = max(4, RoundUpToPowerOfTwo(h)); - - SS.bgImage.fromFile = (uint8_t *)MemAlloc(rw*rh*3); - {for(int i = 0; i < h; i++) { - memcpy(SS.bgImage.fromFile + ((h - 1) - i)*(rw*3), rows[i], w*3); - }} - SS.bgImage.w = w; - SS.bgImage.h = h; - SS.bgImage.rw = rw; - SS.bgImage.rh = rh; - SS.bgImage.scale = SS.GW.scale; - SS.bgImage.origin = SS.GW.offset.ScaledBy(-1); - -err: - if(png_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, NULL); - if(f) fclose(f); - } - SS.ScheduleShowTW(); -} - -void TextWindow::ScreenChangeBackgroundImageScale(int link, uint32_t v) { - SS.TW.edit.meaning = EDIT_BACKGROUND_IMG_SCALE; - SS.TW.ShowEditControl(10, ssprintf("%.3f", SS.bgImage.scale * SS.MmPerUnit())); -} - -void TextWindow::ShowListOfStyles(void) { +void TextWindow::ShowListOfStyles() { Printf(true, "%Ft color style-name"); bool darkbg = false; @@ -475,25 +422,6 @@ void TextWindow::ShowListOfStyles(void) { rgb.redF(), rgb.greenF(), rgb.blueF(), top[rows-1] + 2, &ScreenChangeBackgroundColor); - Printf(false, ""); - Printf(false, "%Ft background bitmap image%E"); - if(SS.bgImage.fromFile) { - Printf(false, "%Ba %Ftwidth:%E %dpx %Ftheight:%E %dpx", - SS.bgImage.w, SS.bgImage.h); - - Printf(false, " %Ftscale:%E %# px/%s %Fl%Ll%f%D[change]%E", - SS.bgImage.scale*SS.MmPerUnit(), - SS.UnitName(), - &ScreenChangeBackgroundImageScale, top[rows-1] + 2); - - Printf(false, "%Ba %Fl%Lc%fclear background image%E", - &ScreenBackgroundImage); - } else { - Printf(false, "%Ba none - %Fl%Ll%fload background image%E", - &ScreenBackgroundImage); - Printf(false, " (bottom left will be center of view)"); - } - Printf(false, ""); Printf(false, " %Fl%Ll%fload factory defaults%E", &ScreenLoadFactoryDefaultStyles); @@ -505,7 +433,7 @@ void TextWindow::ScreenChangeStyleName(int link, uint32_t v) { Style *s = Style::Get(hs); SS.TW.ShowEditControl(12, s->name); SS.TW.edit.style = hs; - SS.TW.edit.meaning = EDIT_STYLE_NAME; + SS.TW.edit.meaning = Edit::STYLE_NAME; } void TextWindow::ScreenDeleteStyle(int link, uint32_t v) { @@ -517,34 +445,37 @@ void TextWindow::ScreenDeleteStyle(int link, uint32_t v) { // And it will get recreated automatically if something is still using // the style, so no need to do anything else. } - SS.TW.GoToScreen(SCREEN_LIST_OF_STYLES); - InvalidateGraphics(); + SS.TW.GoToScreen(Screen::LIST_OF_STYLES); + SS.GW.Invalidate(); } void TextWindow::ScreenChangeStylePatternType(int link, uint32_t v) { hStyle hs = { v }; Style *s = Style::Get(hs); - s->stippleType = link - 1; + s->stippleType = (StipplePattern)(link - 1); + SS.GW.persistentDirty = true; } void TextWindow::ScreenChangeStyleMetric(int link, uint32_t v) { hStyle hs = { v }; Style *s = Style::Get(hs); double val; - int units, meaning, col; + Style::UnitsAs units; + Edit meaning; + int col; switch(link) { case 't': val = s->textHeight; units = s->textHeightAs; col = 10; - meaning = EDIT_STYLE_TEXT_HEIGHT; + meaning = Edit::STYLE_TEXT_HEIGHT; break; case 's': val = s->stippleScale; units = s->widthAs; col = 17; - meaning = EDIT_STYLE_STIPPLE_PERIOD; + meaning = Edit::STYLE_STIPPLE_PERIOD; break; case 'w': @@ -552,14 +483,14 @@ void TextWindow::ScreenChangeStyleMetric(int link, uint32_t v) { val = s->width; units = s->widthAs; col = 9; - meaning = EDIT_STYLE_WIDTH; + meaning = Edit::STYLE_WIDTH; break; - default: oops(); + default: ssassert(false, "Unexpected link"); } std::string edit_value; - if(units == Style::UNITS_AS_PIXELS) { + if(units == Style::UnitsAs::PIXELS) { edit_value = ssprintf("%.2f", val); } else { edit_value = SS.MmToString(val); @@ -574,24 +505,22 @@ void TextWindow::ScreenChangeStyleTextAngle(int link, uint32_t v) { Style *s = Style::Get(hs); SS.TW.ShowEditControl(9, ssprintf("%.2f", s->textAngle)); SS.TW.edit.style = hs; - SS.TW.edit.meaning = EDIT_STYLE_TEXT_ANGLE; + SS.TW.edit.meaning = Edit::STYLE_TEXT_ANGLE; } void TextWindow::ScreenChangeStyleColor(int link, uint32_t v) { hStyle hs = { v }; Style *s = Style::Get(hs); // Same function used for stroke and fill colors - int em; + Edit em; RgbaColor rgb; if(link == 's') { - em = EDIT_STYLE_COLOR; + em = Edit::STYLE_COLOR; rgb = s->color; } else if(link == 'f') { - em = EDIT_STYLE_FILL_COLOR; + em = Edit::STYLE_FILL_COLOR; rgb = s->fillColor; - } else { - oops(); - } + } else ssassert(false, "Unexpected link"); SS.TW.ShowEditControlWithColorPicker(13, rgb); SS.TW.edit.style = hs; SS.TW.edit.meaning = em; @@ -604,15 +533,15 @@ void TextWindow::ScreenChangeStyleYesNo(int link, uint32_t v) { switch(link) { // Units for the width case 'w': - if(s->widthAs != Style::UNITS_AS_MM) { - s->widthAs = Style::UNITS_AS_MM; + if(s->widthAs != Style::UnitsAs::MM) { + s->widthAs = Style::UnitsAs::MM; s->width /= SS.GW.scale; s->stippleScale /= SS.GW.scale; } break; case 'W': - if(s->widthAs != Style::UNITS_AS_PIXELS) { - s->widthAs = Style::UNITS_AS_PIXELS; + if(s->widthAs != Style::UnitsAs::PIXELS) { + s->widthAs = Style::UnitsAs::PIXELS; s->width *= SS.GW.scale; s->stippleScale *= SS.GW.scale; } @@ -620,15 +549,15 @@ void TextWindow::ScreenChangeStyleYesNo(int link, uint32_t v) { // Units for the height case 'g': - if(s->textHeightAs != Style::UNITS_AS_MM) { - s->textHeightAs = Style::UNITS_AS_MM; + if(s->textHeightAs != Style::UnitsAs::MM) { + s->textHeightAs = Style::UnitsAs::MM; s->textHeight /= SS.GW.scale; } break; case 'G': - if(s->textHeightAs != Style::UNITS_AS_PIXELS) { - s->textHeightAs = Style::UNITS_AS_PIXELS; + if(s->textHeightAs != Style::UnitsAs::PIXELS) { + s->textHeightAs = Style::UnitsAs::PIXELS; s->textHeight *= SS.GW.scale; } break; @@ -647,79 +576,79 @@ void TextWindow::ScreenChangeStyleYesNo(int link, uint32_t v) { // Horizontal text alignment case 'L': - s->textOrigin |= Style::ORIGIN_LEFT; - s->textOrigin &= ~Style::ORIGIN_RIGHT; + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::LEFT); + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::RIGHT); break; case 'H': - s->textOrigin &= ~Style::ORIGIN_LEFT; - s->textOrigin &= ~Style::ORIGIN_RIGHT; + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::LEFT); + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::RIGHT); break; case 'R': - s->textOrigin &= ~Style::ORIGIN_LEFT; - s->textOrigin |= Style::ORIGIN_RIGHT; + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::LEFT); + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::RIGHT); break; // Vertical text alignment case 'B': - s->textOrigin |= Style::ORIGIN_BOT; - s->textOrigin &= ~Style::ORIGIN_TOP; + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::BOT); + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::TOP); break; case 'V': - s->textOrigin &= ~Style::ORIGIN_BOT; - s->textOrigin &= ~Style::ORIGIN_TOP; + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::BOT); + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::TOP); break; case 'T': - s->textOrigin &= ~Style::ORIGIN_BOT; - s->textOrigin |= Style::ORIGIN_TOP; + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin & ~(uint32_t)Style::TextOrigin::BOT); + s->textOrigin = (Style::TextOrigin)((uint32_t)s->textOrigin | (uint32_t)Style::TextOrigin::TOP); break; } - InvalidateGraphics(); + SS.GW.Invalidate(/*clearPersistent=*/true); } -bool TextWindow::EditControlDoneForStyles(const char *str) { +bool TextWindow::EditControlDoneForStyles(const std::string &str) { Style *s; switch(edit.meaning) { - case EDIT_STYLE_STIPPLE_PERIOD: - case EDIT_STYLE_TEXT_HEIGHT: - case EDIT_STYLE_WIDTH: { + case Edit::STYLE_STIPPLE_PERIOD: + case Edit::STYLE_TEXT_HEIGHT: + case Edit::STYLE_WIDTH: { SS.UndoRemember(); s = Style::Get(edit.style); double v; - int units = (edit.meaning == EDIT_STYLE_TEXT_HEIGHT) ? + Style::UnitsAs units = (edit.meaning == Edit::STYLE_TEXT_HEIGHT) ? s->textHeightAs : s->widthAs; - if(units == Style::UNITS_AS_MM) { + if(units == Style::UnitsAs::MM) { v = SS.StringToMm(str); } else { - v = atof(str); + v = atof(str.c_str()); } v = max(0.0, v); - if(edit.meaning == EDIT_STYLE_TEXT_HEIGHT) { + if(edit.meaning == Edit::STYLE_TEXT_HEIGHT) { s->textHeight = v; - } else if(edit.meaning == EDIT_STYLE_STIPPLE_PERIOD) { + } else if(edit.meaning == Edit::STYLE_STIPPLE_PERIOD) { s->stippleScale = v; } else { s->width = v; } break; } - case EDIT_STYLE_TEXT_ANGLE: + case Edit::STYLE_TEXT_ANGLE: SS.UndoRemember(); s = Style::Get(edit.style); - s->textAngle = WRAP_SYMMETRIC(atof(str), 360); + s->textAngle = WRAP_SYMMETRIC(atof(str.c_str()), 360); break; - case EDIT_BACKGROUND_COLOR: - case EDIT_STYLE_FILL_COLOR: - case EDIT_STYLE_COLOR: { + case Edit::BACKGROUND_COLOR: + case Edit::STYLE_FILL_COLOR: + case Edit::STYLE_COLOR: { Vector rgb; - if(sscanf(str, "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) { + if(sscanf(str.c_str(), "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) { rgb = rgb.ClampWithin(0, 1); - if(edit.meaning == EDIT_STYLE_COLOR) { + if(edit.meaning == Edit::STYLE_COLOR) { SS.UndoRemember(); s = Style::Get(edit.style); s->color = RGBf(rgb.x, rgb.y, rgb.z); - } else if(edit.meaning == EDIT_STYLE_FILL_COLOR) { + } else if(edit.meaning == Edit::STYLE_FILL_COLOR) { SS.UndoRemember(); s = Style::Get(edit.style); s->fillColor = RGBf(rgb.x, rgb.y, rgb.z); @@ -727,13 +656,13 @@ bool TextWindow::EditControlDoneForStyles(const char *str) { SS.backgroundColor = RGBf(rgb.x, rgb.y, rgb.z); } } else { - Error("Bad format: specify color as r, g, b"); + Error(_("Bad format: specify color as r, g, b")); } break; } - case EDIT_STYLE_NAME: - if(!*str) { - Error("Style name cannot be empty"); + case Edit::STYLE_NAME: + if(str.empty()) { + Error(_("Style name cannot be empty")); } else { SS.UndoRemember(); s = Style::Get(edit.style); @@ -741,24 +670,13 @@ bool TextWindow::EditControlDoneForStyles(const char *str) { } break; - case EDIT_BACKGROUND_IMG_SCALE: { - Expr *e = Expr::From(str, true); - if(e) { - double ev = e->Eval(); - if(ev < 0.001 || isnan(ev)) { - Error("Scale must not be zero or negative!"); - } else { - SS.bgImage.scale = ev / SS.MmPerUnit(); - } - } - break; - } default: return false; } + SS.GW.persistentDirty = true; return true; } -void TextWindow::ShowStyleInfo(void) { +void TextWindow::ShowStyleInfo() { Printf(true, "%Fl%f%Ll(back to list of styles)%E", &ScreenShowListOfStyles); Style *s = Style::Get(shown.style); @@ -779,7 +697,7 @@ void TextWindow::ShowStyleInfo(void) { s->h.v, ScreenChangeStyleColor); // The line width, and its units - if(s->widthAs == Style::UNITS_AS_PIXELS) { + if(s->widthAs == Style::UnitsAs::PIXELS) { Printf(false, " %Ftwidth%E %@ %D%f%Lp%Fl[change]%E", s->width, s->h.v, &ScreenChangeStyleMetric, @@ -791,19 +709,17 @@ void TextWindow::ShowStyleInfo(void) { (s->h.v < Style::FIRST_CUSTOM) ? 'w' : 'W'); } - if(s->h.v >= Style::FIRST_CUSTOM) { - if(s->widthAs == Style::UNITS_AS_PIXELS) { - Printf(false, "%Ba %Ftstipple width%E %@ %D%f%Lp%Fl[change]%E", - s->stippleScale, - s->h.v, &ScreenChangeStyleMetric, 's'); - } else { - Printf(false, "%Ba %Ftstipple width%E %s %D%f%Lp%Fl[change]%E", - SS.MmToString(s->stippleScale).c_str(), - s->h.v, &ScreenChangeStyleMetric, 's'); - } + if(s->widthAs == Style::UnitsAs::PIXELS) { + Printf(false, "%Ba %Ftstipple width%E %@ %D%f%Lp%Fl[change]%E", + s->stippleScale, + s->h.v, &ScreenChangeStyleMetric, 's'); + } else { + Printf(false, "%Ba %Ftstipple width%E %s %D%f%Lp%Fl[change]%E", + SS.MmToString(s->stippleScale).c_str(), + s->h.v, &ScreenChangeStyleMetric, 's'); } - bool widthpx = (s->widthAs == Style::UNITS_AS_PIXELS); + bool widthpx = (s->widthAs == Style::UnitsAs::PIXELS); if(s->h.v < Style::FIRST_CUSTOM) { Printf(false," %Ftin units of %Fdpixels%E"); } else { @@ -819,9 +735,10 @@ void TextWindow::ShowStyleInfo(void) { Printf(false,"%Ba %Ftstipple type:%E"); - const size_t patternCount = Style::LAST_STIPPLE + 1; + const size_t patternCount = (size_t)StipplePattern::LAST + 1; const char *patternsSource[patternCount] = { "___________", + "- - - - ", "- - - - - -", "__ __ __ __", "-.-.-.-.-.-", @@ -832,7 +749,7 @@ void TextWindow::ShowStyleInfo(void) { }; std::string patterns[patternCount]; - for(int i = 0; i <= Style::LAST_STIPPLE; i++) { + for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) { const char *str = patternsSource[i]; do { switch(*str) { @@ -841,13 +758,13 @@ void TextWindow::ShowStyleInfo(void) { case '_': patterns[i] += "\xEE\x80\x85"; break; case '-': patterns[i] += "\xEE\x80\x86"; break; case '~': patterns[i] += "\xEE\x80\x87"; break; - default: oops(); + default: ssassert(false, "Unexpected stipple pattern element"); } } while(*(++str)); } - for(int i = 0; i <= Style::LAST_STIPPLE; i++) { - const char *radio = s->stippleType == i ? RADIO_TRUE : RADIO_FALSE; + for(uint32_t i = 0; i <= (uint32_t)StipplePattern::LAST; i++) { + const char *radio = s->stippleType == (StipplePattern)i ? RADIO_TRUE : RADIO_FALSE; Printf(false, "%Bp %D%f%Lp%s %s%E", (i % 2 == 0) ? 'd' : 'a', s->h.v, &ScreenChangeStylePatternType, @@ -874,7 +791,7 @@ void TextWindow::ShowStyleInfo(void) { Printf(false, ""); Printf(false, "%Ft text style%E"); - if(s->textHeightAs == Style::UNITS_AS_PIXELS) { + if(s->textHeightAs == Style::UnitsAs::PIXELS) { Printf(false, "%Ba %Ftheight %E%@ %D%f%Lt%Fl%s%E", s->textHeight, s->h.v, &ScreenChangeStyleMetric, @@ -886,7 +803,7 @@ void TextWindow::ShowStyleInfo(void) { "[change]"); } - bool textHeightpx = (s->textHeightAs == Style::UNITS_AS_PIXELS); + bool textHeightpx = (s->textHeightAs == Style::UnitsAs::PIXELS); if(s->h.v < Style::FIRST_CUSTOM) { Printf(false,"%Bd %Ftin units of %Fdpixels"); } else { @@ -908,29 +825,29 @@ void TextWindow::ShowStyleInfo(void) { Printf(false, ""); Printf(false, "%Ft text comment alignment%E"); bool neither; - neither = !(s->textOrigin & (Style::ORIGIN_LEFT | Style::ORIGIN_RIGHT)); + neither = !((uint32_t)s->textOrigin & ((uint32_t)Style::TextOrigin::LEFT | (uint32_t)Style::TextOrigin::RIGHT)); Printf(false, "%Ba " "%D%f%LL%s left%E " "%D%f%LH%s center%E " "%D%f%LR%s right%E ", s->h.v, &ScreenChangeStyleYesNo, - (s->textOrigin & Style::ORIGIN_LEFT) ? RADIO_TRUE : RADIO_FALSE, + ((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::LEFT) ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, neither ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, - (s->textOrigin & Style::ORIGIN_RIGHT) ? RADIO_TRUE : RADIO_FALSE); + ((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::RIGHT) ? RADIO_TRUE : RADIO_FALSE); - neither = !(s->textOrigin & (Style::ORIGIN_BOT | Style::ORIGIN_TOP)); + neither = !((uint32_t)s->textOrigin & ((uint32_t)Style::TextOrigin::BOT | (uint32_t)Style::TextOrigin::TOP)); Printf(false, "%Bd " "%D%f%LB%s bottom%E " "%D%f%LV%s center%E " "%D%f%LT%s top%E ", s->h.v, &ScreenChangeStyleYesNo, - (s->textOrigin & Style::ORIGIN_BOT) ? RADIO_TRUE : RADIO_FALSE, + ((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::BOT) ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, neither ? RADIO_TRUE : RADIO_FALSE, s->h.v, &ScreenChangeStyleYesNo, - (s->textOrigin & Style::ORIGIN_TOP) ? RADIO_TRUE : RADIO_FALSE); + ((uint32_t)s->textOrigin & (uint32_t)Style::TextOrigin::TOP) ? RADIO_TRUE : RADIO_FALSE); } if(s->h.v >= Style::FIRST_CUSTOM) { @@ -953,4 +870,3 @@ void TextWindow::ShowStyleInfo(void) { void TextWindow::ScreenAssignSelectionToStyle(int link, uint32_t v) { Style::AssignSelectionToStyle(v); } - diff --git a/src/system.cpp b/src/system.cpp index 6b222b9..1119c34 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -20,28 +20,29 @@ const double System::RANK_MAG_TOLERANCE = 1e-4; const double System::CONVERGE_TOLERANCE = (LENGTH_EPS/(1e2)); bool System::WriteJacobian(int tag) { - int a, i, j; - j = 0; - for(a = 0; a < param.n; a++) { - if(j >= MAX_UNKNOWNS) return false; + int j = 0; + for(auto &p : param) { + if(j >= MAX_UNKNOWNS) + return false; - Param *p = &(param.elem[a]); - if(p->tag != tag) continue; - mat.param[j] = p->h; + if(p.tag != tag) + continue; + mat.param[j] = p.h; j++; } mat.n = j; - i = 0; - for(a = 0; a < eq.n; a++) { + int i = 0; + + for(auto &e : eq) { if(i >= MAX_UNKNOWNS) return false; - Equation *e = &(eq.elem[a]); - if(e->tag != tag) continue; + if(e.tag != tag) + continue; - mat.eq[i] = e->h; - Expr *f = e->e->DeepCopyWithParamsAsPointers(¶m, &(SK.param)); + mat.eq[i] = e.h; + Expr *f = e.e->DeepCopyWithParamsAsPointers(¶m, &(SK.param)); f = f->FoldConstants(); // Hash table (61 bits) to accelerate generation of zero partials. @@ -67,7 +68,7 @@ bool System::WriteJacobian(int tag) { return true; } -void System::EvalJacobian(void) { +void System::EvalJacobian() { int i, j; for(i = 0; i < mat.m; i++) { for(j = 0; j < mat.n; j++) { @@ -79,20 +80,18 @@ void System::EvalJacobian(void) { bool System::IsDragged(hParam p) { hParam *pp; for(pp = dragged.First(); pp; pp = dragged.NextAfter(pp)) { - if(p.v == pp->v) return true; + if(p == *pp) return true; } return false; } -void System::SolveBySubstitution(void) { - int i; - for(i = 0; i < eq.n; i++) { - Equation *teq = &(eq.elem[i]); - Expr *tex = teq->e; +void System::SolveBySubstitution() { + for(auto &teq : eq) { + Expr *tex = teq.e; - if(tex->op == Expr::MINUS && - tex->a->op == Expr::PARAM && - tex->b->op == Expr::PARAM) + if(tex->op == Expr::Op::MINUS && + tex->a->op == Expr::Op::PARAM && + tex->b->op == Expr::Op::PARAM) { hParam a = tex->a->parh; hParam b = tex->b->parh; @@ -105,27 +104,22 @@ void System::SolveBySubstitution(void) { if(IsDragged(a)) { // A is being dragged, so A should stay, and B should go - hParam t = a; - a = b; - b = t; + std::swap(a, b); } - int j; - for(j = 0; j < eq.n; j++) { - Equation *req = &(eq.elem[j]); - (req->e)->Substitute(a, b); // A becomes B, B unchanged + for(auto &req : eq) { + req.e->Substitute(a, b); // A becomes B, B unchanged } - for(j = 0; j < param.n; j++) { - Param *rp = &(param.elem[j]); - if(rp->substd.v == a.v) { - rp->substd = b; + for(auto &rp : param) { + if(rp.substd == a) { + rp.substd = b; } } Param *ptr = param.FindById(a); ptr->tag = VAR_SUBSTITUTED; ptr->substd = b; - teq->tag = EQ_SUBSTITUTED; + teq.tag = EQ_SUBSTITUTED; } } } @@ -135,7 +129,7 @@ void System::SolveBySubstitution(void) { // in place. A row (~equation) is considered to be all zeros if its magnitude // is less than the tolerance RANK_MAG_TOLERANCE. //----------------------------------------------------------------------------- -int System::CalculateRank(void) { +int System::CalculateRank() { // Actually work with magnitudes squared, not the magnitudes double rowMag[MAX_UNKNOWNS] = {}; double tol = RANK_MAG_TOLERANCE*RANK_MAG_TOLERANCE; @@ -172,9 +166,11 @@ int System::CalculateRank(void) { return rank; } -bool System::TestRank(void) { +bool System::TestRank(int *rank) { EvalJacobian(); - return CalculateRank() == mat.m; + int jacobianRank = CalculateRank(); + if(rank) *rank = jacobianRank; + return jacobianRank == mat.m; } bool System::SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS], @@ -191,15 +187,15 @@ bool System::SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS], // greater. First, find a pivot (between rows i and N-1). max = 0; for(ip = i; ip < n; ip++) { - if(ffabs(A[ip][i]) > max) { + if(fabs(A[ip][i]) > max) { imax = ip; - max = ffabs(A[ip][i]); + max = fabs(A[ip][i]); } } // Don't give up on a singular matrix unless it's really bad; the // assumption code is responsible for identifying that condition, // so we're not responsible for reporting that error. - if(ffabs(max) < 1e-20) continue; + if(fabs(max) < 1e-20) continue; // Swap row imax with row i for(jp = 0; jp < n; jp++) { @@ -221,7 +217,7 @@ bool System::SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS], // We've put the matrix in upper triangular form, so at this point we // can solve by back-substitution. for(i = n - 1; i >= 0; i--) { - if(ffabs(A[i][i]) < 1e-20) continue; + if(fabs(A[i][i]) < 1e-20) continue; temp = B[i]; for(j = n - 1; j > i; j--) { @@ -233,7 +229,7 @@ bool System::SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS], return true; } -bool System::SolveLeastSquares(void) { +bool System::SolveLeastSquares() { int r, c, i; // Scale the columns; this scale weights the parameters for the least @@ -297,7 +293,7 @@ bool System::NewtonSolve(int tag) { for(i = 0; i < mat.n; i++) { Param *p = param.FindById(mat.param[i]); p->val -= mat.X[i]; - if(isnan(p->val)) { + if(IsReasonable(p->val)) { // Very bad, and clearly not convergent return false; } @@ -310,10 +306,10 @@ bool System::NewtonSolve(int tag) { // Check for convergence converged = true; for(i = 0; i < mat.m; i++) { - if(isnan(mat.B.num[i])) { + if(IsReasonable(mat.B.num[i])) { return false; } - if(ffabs(mat.B.num[i]) > CONVERGE_TOLERANCE) { + if(fabs(mat.B.num[i]) > CONVERGE_TOLERANCE) { converged = false; break; } @@ -324,14 +320,13 @@ bool System::NewtonSolve(int tag) { } void System::WriteEquationsExceptFor(hConstraint hc, Group *g) { - int i; // Generate all the equations from constraints in this group - for(i = 0; i < SK.constraint.n; i++) { - ConstraintBase *c = &(SK.constraint.elem[i]); - if(c->group.v != g->h.v) continue; - if(c->h.v == hc.v) continue; + for(auto &con : SK.constraint) { + ConstraintBase *c = &con; + if(c->group != g->h) continue; + if(c->h == hc) continue; - if(c->HasLabel() && c->type != Constraint::COMMENT && + if(c->HasLabel() && c->type != Constraint::Type::COMMENT && g->allDimsReference) { // When all dimensions are reference, we adjust them to display @@ -339,19 +334,19 @@ void System::WriteEquationsExceptFor(hConstraint hc, Group *g) { c->ModifyToSatisfy(); continue; } - if(g->relaxConstraints && c->type != Constraint::POINTS_COINCIDENT) { + if(g->relaxConstraints && c->type != Constraint::Type::POINTS_COINCIDENT) { // When the constraints are relaxed, we keep only the point- // coincident constraints, and the constraints generated by // the entities and groups. continue; } - c->Generate(&eq); + c->GenerateEquations(&eq); } // And the equations from entities - for(i = 0; i < SK.entity.n; i++) { - EntityBase *e = &(SK.entity.elem[i]); - if(e->group.v != g->h.v) continue; + for(auto &ent : SK.entity) { + EntityBase *e = &ent; + if(e->group != g->h) continue; e->GenerateEquations(&eq); } @@ -359,15 +354,22 @@ void System::WriteEquationsExceptFor(hConstraint hc, Group *g) { g->GenerateEquations(&eq); } -void System::FindWhichToRemoveToFixJacobian(Group *g, List *bad) { - int a, i; +void System::FindWhichToRemoveToFixJacobian(Group *g, List *bad, bool forceDofCheck) { + auto time = GetMilliseconds(); + g->solved.timeout = false; + int a; for(a = 0; a < 2; a++) { - for(i = 0; i < SK.constraint.n; i++) { - ConstraintBase *c = &(SK.constraint.elem[i]); - if(c->group.v != g->h.v) continue; - if((c->type == Constraint::POINTS_COINCIDENT && a == 0) || - (c->type != Constraint::POINTS_COINCIDENT && a == 1)) + for(auto &con : SK.constraint) { + if((GetMilliseconds() - time) > g->solved.findToFixTimeout) { + g->solved.timeout = true; + return; + } + + ConstraintBase *c = &con; + if(c->group != g->h) continue; + if((c->type == Constraint::Type::POINTS_COINCIDENT && a == 0) || + (c->type != Constraint::Type::POINTS_COINCIDENT && a == 1)) { // Do the constraints in two passes: first everything but // the point-coincident constraints, then only those @@ -382,7 +384,9 @@ void System::FindWhichToRemoveToFixJacobian(Group *g, List *bad) { // It's a major speedup to solve the easy ones by substitution here, // and that doesn't break anything. - SolveBySubstitution(); + if(!forceDofCheck) { + SolveBySubstitution(); + } WriteJacobian(0); EvalJacobian(); @@ -396,8 +400,8 @@ void System::FindWhichToRemoveToFixJacobian(Group *g, List *bad) { } } -int System::Solve(Group *g, int *dof, List *bad, - bool andFindBad, bool andFindFree) +SolveResult System::Solve(Group *g, int *rank, int *dof, List *bad, + bool andFindBad, bool andFindFree, bool forceDofCheck) { WriteEquationsExceptFor(Constraint::NO_CONSTRAINT, g); @@ -407,39 +411,46 @@ int System::Solve(Group *g, int *dof, List *bad, /* dbp("%d equations", eq.n); for(i = 0; i < eq.n; i++) { - dbp(" %.3f = %s = 0", eq.elem[i].e->Eval(), eq.elem[i].e->Print()); + dbp(" %.3f = %s = 0", eq[i].e->Eval(), eq[i].e->Print()); } dbp("%d parameters", param.n); for(i = 0; i < param.n; i++) { - dbp(" param %08x at %.3f", param.elem[i].h.v, param.elem[i].val); + dbp(" param %08x at %.3f", param[i].h.v, param[i].val); } */ // All params and equations are assigned to group zero. param.ClearTags(); eq.ClearTags(); - SolveBySubstitution(); + // Solving by substitution eliminates duplicate e.g. H/V constraints, which can cause rank test + // to succeed even on overdefined systems, which will fail later. + if(!forceDofCheck) { + SolveBySubstitution(); + } // Before solving the big system, see if we can find any equations that // are soluble alone. This can be a huge speedup. We don't know whether // the system is consistent yet, but if it isn't then we'll catch that // later. int alone = 1; - for(i = 0; i < eq.n; i++) { - Equation *e = &(eq.elem[i]); - if(e->tag != 0) continue; + for(auto &e : eq) { + if(e.tag != 0) + continue; - hParam hp = e->e->ReferencedParams(¶m); - if(hp.v == Expr::NO_PARAMS.v) continue; - if(hp.v == Expr::MULTIPLE_PARAMS.v) continue; + hParam hp = e.e->ReferencedParams(¶m); + if(hp == Expr::NO_PARAMS) continue; + if(hp == Expr::MULTIPLE_PARAMS) continue; Param *p = param.FindById(hp); if(p->tag != 0) continue; // let rank test catch inconsistency - e->tag = alone; + e.tag = alone; p->tag = alone; WriteJacobian(alone); if(!NewtonSolve(alone)) { + // We don't do the rank test, so let's arbitrarily return + // the DIDNT_CONVERGE result here. + rankOk = true; // Failed to converge, bail out early goto didnt_converge; } @@ -449,69 +460,47 @@ int System::Solve(Group *g, int *dof, List *bad, // Now write the Jacobian for what's left, and do a rank test; that // tells us if the system is inconsistently constrained. if(!WriteJacobian(0)) { - return System::TOO_MANY_UNKNOWNS; + return SolveResult::TOO_MANY_UNKNOWNS; } - rankOk = TestRank(); + rankOk = TestRank(rank); // And do the leftovers as one big system if(!NewtonSolve(0)) { goto didnt_converge; } - rankOk = TestRank(); + rankOk = TestRank(rank); if(!rankOk) { - if(!g->allowRedundant) { - if(andFindBad) FindWhichToRemoveToFixJacobian(g, bad); - } + if(andFindBad) FindWhichToRemoveToFixJacobian(g, bad, forceDofCheck); } else { // This is not the full Jacobian, but any substitutions or single-eq // solves removed one equation and one unknown, therefore no effect // on the number of DOF. - if(dof) *dof = mat.n - mat.m; - - // If requested, find all the free (unbound) variables. This might be - // more than the number of degrees of freedom. Don't always do this, - // because the display would get annoying and it's slow. - for(i = 0; i < param.n; i++) { - Param *p = &(param.elem[i]); - p->free = false; - - if(andFindFree) { - if(p->tag == 0) { - p->tag = VAR_DOF_TEST; - WriteJacobian(0); - EvalJacobian(); - int rank = CalculateRank(); - if(rank == mat.m) { - p->free = true; - } - p->tag = 0; - } - } - } + if(dof) *dof = CalculateDof(); + MarkParamsFree(andFindFree); } // System solved correctly, so write the new values back in to the // main parameter table. - for(i = 0; i < param.n; i++) { - Param *p = &(param.elem[i]); + for(auto &p : param) { double val; - if(p->tag == VAR_SUBSTITUTED) { - val = param.FindById(p->substd)->val; + if(p.tag == VAR_SUBSTITUTED) { + val = param.FindById(p.substd)->val; } else { - val = p->val; + val = p.val; } - Param *pp = SK.GetParam(p->h); + Param *pp = SK.GetParam(p.h); pp->val = val; pp->known = true; - pp->free = p->free; + pp->free = p.free; } - return rankOk ? System::SOLVED_OKAY : System::REDUNDANT_OKAY; + return rankOk ? SolveResult::OKAY : SolveResult::REDUNDANT_OKAY; didnt_converge: SK.constraint.ClearTags(); + // Not using range-for here because index is used in additional ways for(i = 0; i < eq.n; i++) { - if(ffabs(mat.B.num[i]) > CONVERGE_TOLERANCE || isnan(mat.B.num[i])) { + if(fabs(mat.B.num[i]) > CONVERGE_TOLERANCE || IsReasonable(mat.B.num[i])) { // This constraint is unsatisfied. if(!mat.eq[i].isFromConstraint()) continue; @@ -527,12 +516,64 @@ didnt_converge: } } - return rankOk ? System::DIDNT_CONVERGE : System::REDUNDANT_DIDNT_CONVERGE; + return rankOk ? SolveResult::DIDNT_CONVERGE : SolveResult::REDUNDANT_DIDNT_CONVERGE; +} + +SolveResult System::SolveRank(Group *g, int *rank, int *dof, List *bad, + bool andFindBad, bool andFindFree) +{ + WriteEquationsExceptFor(Constraint::NO_CONSTRAINT, g); + + // All params and equations are assigned to group zero. + param.ClearTags(); + eq.ClearTags(); + + // Now write the Jacobian, and do a rank test; that + // tells us if the system is inconsistently constrained. + if(!WriteJacobian(0)) { + return SolveResult::TOO_MANY_UNKNOWNS; + } + + bool rankOk = TestRank(rank); + if(!rankOk) { + if(andFindBad) FindWhichToRemoveToFixJacobian(g, bad, /*forceDofCheck=*/true); + } else { + if(dof) *dof = CalculateDof(); + MarkParamsFree(andFindFree); + } + return rankOk ? SolveResult::OKAY : SolveResult::REDUNDANT_OKAY; } -void System::Clear(void) { +void System::Clear() { entity.Clear(); param.Clear(); eq.Clear(); dragged.Clear(); } + +void System::MarkParamsFree(bool find) { + // If requested, find all the free (unbound) variables. This might be + // more than the number of degrees of freedom. Don't always do this, + // because the display would get annoying and it's slow. + for(auto &p : param) { + p.free = false; + + if(find) { + if(p.tag == 0) { + p.tag = VAR_DOF_TEST; + WriteJacobian(0); + EvalJacobian(); + int rank = CalculateRank(); + if(rank == mat.m) { + p.free = true; + } + p.tag = 0; + } + } + } +} + +int System::CalculateDof() { + return mat.n - mat.m; +} + diff --git a/src/textscreens.cpp b/src/textscreens.cpp index e00b2bd..aac2703 100644 --- a/src/textscreens.cpp +++ b/src/textscreens.cpp @@ -11,7 +11,7 @@ // link to bring us back home. //----------------------------------------------------------------------------- void TextWindow::ScreenHome(int link, uint32_t v) { - SS.TW.GoToScreen(SCREEN_LIST_OF_GROUPS); + SS.TW.GoToScreen(Screen::LIST_OF_GROUPS); } void TextWindow::ShowHeader(bool withNav) { ClearScreen(); @@ -44,7 +44,8 @@ void TextWindow::ShowHeader(bool withNav) { // to hide or show them, and to view them in detail. This is our home page. //----------------------------------------------------------------------------- void TextWindow::ScreenSelectGroup(int link, uint32_t v) { - SS.TW.GoToScreen(SCREEN_GROUP_INFO); + GraphicsWindow::MenuEdit(Command::UNSELECT_ALL); + SS.TW.GoToScreen(Screen::GROUP_INFO); SS.TW.shown.group.v = v; } void TextWindow::ScreenToggleGroupShown(int link, uint32_t v) { @@ -57,10 +58,12 @@ void TextWindow::ScreenToggleGroupShown(int link, uint32_t v) { } void TextWindow::ScreenShowGroupsSpecial(int link, uint32_t v) { bool state = link == 's'; - for(int i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); + for(hGroup hg : SK.groupOrder) { + Group *g = SK.GetGroup(hg); + g->visible = state; } + SS.GW.persistentDirty = true; } void TextWindow::ScreenActivateGroup(int link, uint32_t v) { SS.GW.activeGroup.v = v; @@ -69,66 +72,84 @@ void TextWindow::ScreenActivateGroup(int link, uint32_t v) { } void TextWindow::ReportHowGroupSolved(hGroup hg) { SS.GW.ClearSuper(); - SS.TW.GoToScreen(SCREEN_GROUP_SOLVE_INFO); - SS.TW.shown.group.v = hg.v; + SS.TW.GoToScreen(Screen::GROUP_SOLVE_INFO); + SS.TW.shown.group = hg; SS.ScheduleShowTW(); } void TextWindow::ScreenHowGroupSolved(int link, uint32_t v) { if(SS.GW.activeGroup.v != v) { ScreenActivateGroup(link, v); } - SS.TW.GoToScreen(SCREEN_GROUP_SOLVE_INFO); + SS.TW.GoToScreen(Screen::GROUP_SOLVE_INFO); SS.TW.shown.group.v = v; } void TextWindow::ScreenShowConfiguration(int link, uint32_t v) { - SS.TW.GoToScreen(SCREEN_CONFIGURATION); + SS.TW.GoToScreen(Screen::CONFIGURATION); } void TextWindow::ScreenShowEditView(int link, uint32_t v) { - SS.TW.GoToScreen(SCREEN_EDIT_VIEW); + SS.TW.GoToScreen(Screen::EDIT_VIEW); } void TextWindow::ScreenGoToWebsite(int link, uint32_t v) { - OpenWebsite("http://solvespace.com/txtlink"); + Platform::OpenInBrowser("http://solvespace.com/txtlink"); } -void TextWindow::ShowListOfGroups(void) { +void TextWindow::ShowListOfGroups() { const char *radioTrue = " " RADIO_TRUE " ", *radioFalse = " " RADIO_FALSE " ", *checkTrue = " " CHECK_TRUE " ", *checkFalse = " " CHECK_FALSE " "; Printf(true, "%Ft active"); - Printf(false, "%Ft shown ok group-name%E"); - int i; + Printf(false, "%Ft shown dof group-name%E"); bool afterActive = false; - for(i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); + bool backgroundParity = false; + for(hGroup hg : SK.groupOrder) { + Group *g = SK.GetGroup(hg); + std::string s = g->DescriptionString(); - bool active = (g->h.v == SS.GW.activeGroup.v); + bool active = (g->h == SS.GW.activeGroup); bool shown = g->visible; bool ok = g->IsSolvedOkay(); - bool ref = (g->h.v == Group::HGROUP_REFERENCES.v); - Printf(false, "%Bp%Fd " + bool warn = (g->type == Group::Type::DRAWING_WORKPLANE && + g->polyError.how != PolyError::GOOD) || + ((g->type == Group::Type::EXTRUDE || + g->type == Group::Type::LATHE) && + SK.GetGroup(g->opA)->polyError.how != PolyError::GOOD); + int dof = g->solved.dof; + char sdof[16] = "ok "; + if(ok && dof > 0) { + if(dof > 999) { + strcpy(sdof, "###"); + } else { + sprintf(sdof, "%-3d", dof); + } + } + bool ref = (g->h == Group::HGROUP_REFERENCES); + Printf(false, + "%Bp%Fd " "%Ft%s%Fb%D%f%Ll%s%E " "%Fb%s%D%f%Ll%s%E " - "%Fp%D%f%s%Ll%s%E " + "%Fp%D%f%s%Ll%s%E " "%Fl%Ll%D%f%s", - // Alternate between light and dark backgrounds, for readability - (i & 1) ? 'd' : 'a', - // Link that activates the group - ref ? " " : "", - g->h.v, (&TextWindow::ScreenActivateGroup), - ref ? "" : (active ? radioTrue : radioFalse), - // Link that hides or shows the group - afterActive ? " - " : "", - g->h.v, (&TextWindow::ScreenToggleGroupShown), - afterActive ? "" : (shown ? checkTrue : checkFalse), - // Link to the errors, if a problem occured while solving - ok ? 's' : 'x', g->h.v, (&TextWindow::ScreenHowGroupSolved), - ok ? "ok" : "", - ok ? "" : "NO", - // Link to a screen that gives more details on the group - g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str()); + // Alternate between light and dark backgrounds, for readability + backgroundParity ? 'd' : 'a', + // Link that activates the group + ref ? " " : "", + g->h.v, (&TextWindow::ScreenActivateGroup), + ref ? "" : (active ? radioTrue : radioFalse), + // Link that hides or shows the group + afterActive ? " - " : "", + g->h.v, (&TextWindow::ScreenToggleGroupShown), + afterActive ? "" : (shown ? checkTrue : checkFalse), + // Link to the errors, if a problem occurred while solving + ok ? (warn ? 'm' : (dof > 0 ? 'i' : 's')) : 'x', + g->h.v, (&TextWindow::ScreenHowGroupSolved), + ok ? (warn ? "err" : sdof) : "", + ok ? "" : "ERR", + // Link to a screen that gives more details on the group + g->h.v, (&TextWindow::ScreenSelectGroup), s.c_str()); if(active) afterActive = true; + backgroundParity = !backgroundParity; } Printf(true, " %Fl%Ls%fshow all%E / %Fl%Lh%fhide all%E", @@ -147,17 +168,16 @@ void TextWindow::ShowListOfGroups(void) { // The screen that shows information about a specific group, and allows the // user to edit various things about it. //----------------------------------------------------------------------------- -void TextWindow::ScreenHoverConstraint(int link, uint32_t v) { - if(!SS.GW.showConstraints) return; - - hConstraint hc = { v }; - Constraint *c = SK.GetConstraint(hc); - if(c->group.v != SS.GW.activeGroup.v) { - // Only constraints in the active group are visible - return; - } +void TextWindow::ScreenHoverGroupWorkplane(int link, uint32_t v) { SS.GW.hover.Clear(); - SS.GW.hover.constraint = hc; + hGroup hg = { v }; + SS.GW.hover.entity = hg.entity(0); + SS.GW.hover.emphasized = true; +} +void TextWindow::ScreenHoverEntity(int link, uint32_t v) { + SS.GW.hover.Clear(); + hEntity he = { v }; + SS.GW.hover.entity = he; SS.GW.hover.emphasized = true; } void TextWindow::ScreenHoverRequest(int link, uint32_t v) { @@ -166,10 +186,19 @@ void TextWindow::ScreenHoverRequest(int link, uint32_t v) { SS.GW.hover.entity = hr.entity(0); SS.GW.hover.emphasized = true; } -void TextWindow::ScreenSelectConstraint(int link, uint32_t v) { +void TextWindow::ScreenHoverConstraint(int link, uint32_t v) { + if(!SS.GW.showConstraints) return; + + hConstraint hc = { v }; + SS.GW.hover.Clear(); + SS.GW.hover.constraint = hc; + SS.GW.hover.emphasized = true; +} +void TextWindow::ScreenSelectEntity(int link, uint32_t v) { SS.GW.ClearSelection(); GraphicsWindow::Selection sel = {}; - sel.constraint.v = v; + hEntity he = { v }; + sel.entity = he; SS.GW.selection.Add(&sel); } void TextWindow::ScreenSelectRequest(int link, uint32_t v) { @@ -179,28 +208,44 @@ void TextWindow::ScreenSelectRequest(int link, uint32_t v) { sel.entity = hr.entity(0); SS.GW.selection.Add(&sel); } +void TextWindow::ScreenSelectConstraint(int link, uint32_t v) { + SS.GW.ClearSelection(); + GraphicsWindow::Selection sel = {}; + sel.constraint.v = v; + SS.GW.selection.Add(&sel); +} void TextWindow::ScreenChangeGroupOption(int link, uint32_t v) { SS.UndoRemember(); Group *g = SK.GetGroup(SS.TW.shown.group); switch(link) { - case 's': g->subtype = Group::ONE_SIDED; break; - case 'S': g->subtype = Group::TWO_SIDED; break; + case 's': g->subtype = Group::Subtype::ONE_SIDED; break; + case 'S': g->subtype = Group::Subtype::TWO_SIDED; break; case 'k': g->skipFirst = true; break; case 'K': g->skipFirst = false; break; case 'c': - // When an extrude group is first created, it's positioned for a union - // extrusion. If no constraints were added, flip it when we switch between - // union and difference modes to avoid manual work doing the smae. - if(g->meshCombine != (int)v && g->GetNumConstraints() == 0 && - (v == Group::COMBINE_AS_DIFFERENCE || - g->meshCombine == Group::COMBINE_AS_DIFFERENCE)) { - g->ExtrusionForceVectorTo(g->ExtrusionGetVector().Negated()); + if(g->type == Group::Type::EXTRUDE) { + // When an extrude group is first created, it's positioned for a union + // extrusion. If no constraints were added, flip it when we switch between + // union/assemble and difference/intersection modes to avoid manual work doing the same. + if(g->meshCombine != (Group::CombineAs)v && g->GetNumConstraints() == 0) { + // I apologise for this if statement + if((((Group::CombineAs::DIFFERENCE == g->meshCombine) || + (Group::CombineAs::INTERSECTION == g->meshCombine)) && + (Group::CombineAs::DIFFERENCE != (Group::CombineAs)v) && + (Group::CombineAs::INTERSECTION != (Group::CombineAs)v)) || + ((Group::CombineAs::DIFFERENCE != g->meshCombine) && + (Group::CombineAs::INTERSECTION != g->meshCombine) && + ((Group::CombineAs::DIFFERENCE == (Group::CombineAs)v) || + (Group::CombineAs::INTERSECTION == (Group::CombineAs)v)))) { + g->ExtrusionForceVectorTo(g->ExtrusionGetVector().Negated()); + } + } } - g->meshCombine = v; + g->meshCombine = (Group::CombineAs)v; break; case 'P': g->suppress = !(g->suppress); break; @@ -217,7 +262,6 @@ void TextWindow::ScreenChangeGroupOption(int link, uint32_t v) { } SS.MarkGroupDirty(g->h); - SS.GenerateAll(); SS.GW.ClearSuper(); } @@ -226,40 +270,40 @@ void TextWindow::ScreenColor(int link, uint32_t v) { Group *g = SK.GetGroup(SS.TW.shown.group); SS.TW.ShowEditControlWithColorPicker(3, g->color); - SS.TW.edit.meaning = EDIT_GROUP_COLOR; + SS.TW.edit.meaning = Edit::GROUP_COLOR; } void TextWindow::ScreenOpacity(int link, uint32_t v) { Group *g = SK.GetGroup(SS.TW.shown.group); SS.TW.ShowEditControl(11, ssprintf("%.2f", g->color.alphaF())); - SS.TW.edit.meaning = EDIT_GROUP_OPACITY; - SS.TW.edit.group.v = g->h.v; + SS.TW.edit.meaning = Edit::GROUP_OPACITY; + SS.TW.edit.group = g->h; } void TextWindow::ScreenChangeExprA(int link, uint32_t v) { Group *g = SK.GetGroup(SS.TW.shown.group); SS.TW.ShowEditControl(10, ssprintf("%d", (int)g->valA)); - SS.TW.edit.meaning = EDIT_TIMES_REPEATED; + SS.TW.edit.meaning = Edit::TIMES_REPEATED; SS.TW.edit.group.v = v; } void TextWindow::ScreenChangeGroupName(int link, uint32_t v) { Group *g = SK.GetGroup(SS.TW.shown.group); SS.TW.ShowEditControl(12, g->DescriptionString().substr(5)); - SS.TW.edit.meaning = EDIT_GROUP_NAME; + SS.TW.edit.meaning = Edit::GROUP_NAME; SS.TW.edit.group.v = v; } void TextWindow::ScreenChangeGroupScale(int link, uint32_t v) { Group *g = SK.GetGroup(SS.TW.shown.group); SS.TW.ShowEditControl(13, ssprintf("%.3f", g->scale)); - SS.TW.edit.meaning = EDIT_GROUP_SCALE; + SS.TW.edit.meaning = Edit::GROUP_SCALE; SS.TW.edit.group.v = v; } void TextWindow::ScreenDeleteGroup(int link, uint32_t v) { SS.UndoRemember(); hGroup hg = SS.TW.shown.group; - if(hg.v == SS.GW.activeGroup.v) { + if(hg == SS.GW.activeGroup) { SS.GW.activeGroup = SK.GetGroup(SS.GW.activeGroup)->PreviousGroup()->h; } @@ -269,17 +313,17 @@ void TextWindow::ScreenDeleteGroup(int link, uint32_t v) { // This is a major change, so let's re-solve everything. SK.group.RemoveById(hg); - SS.GenerateAll(SolveSpaceUI::GENERATE_ALL); + SS.GenerateAll(SolveSpaceUI::Generate::ALL); // Reset the graphics window. This will also recreate the default // group if it was removed. SS.GW.ClearSuper(); } -void TextWindow::ShowGroupInfo(void) { +void TextWindow::ShowGroupInfo() { Group *g = SK.GetGroup(shown.group); const char *s = "???"; - if(shown.group.v == Group::HGROUP_REFERENCES.v) { + if(shown.group == Group::HGROUP_REFERENCES) { Printf(true, "%FtGROUP %E%s", g->DescriptionString().c_str()); goto list_items; } else { @@ -289,21 +333,25 @@ void TextWindow::ShowGroupInfo(void) { g->h.v, &TextWindow::ScreenDeleteGroup); } - if(g->type == Group::LATHE) { + if(g->type == Group::Type::LATHE) { Printf(true, " %Ftlathe plane sketch"); - } else if(g->type == Group::EXTRUDE || g->type == Group::ROTATE || - g->type == Group::TRANSLATE) - { - if(g->type == Group::EXTRUDE) { + } else if(g->type == Group::Type::EXTRUDE || g->type == Group::Type::ROTATE || + g->type == Group::Type::TRANSLATE || g->type == Group::Type::REVOLVE || + g->type == Group::Type::HELIX) { + if(g->type == Group::Type::EXTRUDE) { s = "extrude plane sketch"; - } else if(g->type == Group::TRANSLATE) { + } else if(g->type == Group::Type::TRANSLATE) { s = "translate original sketch"; - } else if(g->type == Group::ROTATE) { + } else if(g->type == Group::Type::HELIX) { + s = "create helical extrusion"; + } else if(g->type == Group::Type::ROTATE) { s = "rotate original sketch"; + } else if(g->type == Group::Type::REVOLVE) { + s = "revolve original sketch"; } Printf(true, " %Ft%s%E", s); - bool one = (g->subtype == Group::ONE_SIDED); + bool one = (g->subtype == Group::Subtype::ONE_SIDED); Printf(false, "%Ba %f%Ls%Fd%s one-sided%E " "%f%LS%Fd%s two-sided%E", @@ -312,8 +360,8 @@ void TextWindow::ShowGroupInfo(void) { &TextWindow::ScreenChangeGroupOption, !one ? RADIO_TRUE : RADIO_FALSE); - if(g->type == Group::ROTATE || g->type == Group::TRANSLATE) { - if(g->subtype == Group::ONE_SIDED) { + if(g->type == Group::Type::ROTATE || g->type == Group::Type::TRANSLATE) { + if(g->subtype == Group::Subtype::ONE_SIDED) { bool skip = g->skipFirst; Printf(false, "%Bd %Ftstart %f%LK%Fd%s with original%E " @@ -326,52 +374,58 @@ void TextWindow::ShowGroupInfo(void) { int times = (int)(g->valA); Printf(false, "%Bp %Ftrepeat%E %d time%s %Fl%Ll%D%f[change]%E", - (g->subtype == Group::ONE_SIDED) ? 'a' : 'd', + (g->subtype == Group::Subtype::ONE_SIDED) ? 'a' : 'd', times, times == 1 ? "" : "s", g->h.v, &TextWindow::ScreenChangeExprA); } - } else if(g->type == Group::LINKED) { + } else if(g->type == Group::Type::LINKED) { Printf(true, " %Ftlink geometry from file%E"); - Printf(false, "%Ba '%s'", g->linkFileRel.c_str()); + Platform::Path relativePath = g->linkFile.RelativeTo(SS.saveFile.Parent()); + if(relativePath.IsEmpty()) { + Printf(false, "%Ba '%s'", g->linkFile.raw.c_str()); + } else { + Printf(false, "%Ba '%s'", relativePath.raw.c_str()); + } Printf(false, "%Bd %Ftscaled by%E %# %Fl%Ll%f%D[change]%E", g->scale, &TextWindow::ScreenChangeGroupScale, g->h.v); - } else if(g->type == Group::DRAWING_3D) { + } else if(g->type == Group::Type::DRAWING_3D) { Printf(true, " %Ftsketch in 3d%E"); - } else if(g->type == Group::DRAWING_WORKPLANE) { + } else if(g->type == Group::Type::DRAWING_WORKPLANE) { Printf(true, " %Ftsketch in new workplane%E"); } else { Printf(true, "???"); } Printf(false, ""); - if(g->type == Group::EXTRUDE || - g->type == Group::LATHE || - g->type == Group::LINKED) - { - bool un = (g->meshCombine == Group::COMBINE_AS_UNION); - bool diff = (g->meshCombine == Group::COMBINE_AS_DIFFERENCE); - bool asy = (g->meshCombine == Group::COMBINE_AS_ASSEMBLE); - bool asa = (g->type == Group::LINKED); + if(g->type == Group::Type::EXTRUDE || g->type == Group::Type::LATHE || + g->type == Group::Type::REVOLVE || g->type == Group::Type::LINKED || + g->type == Group::Type::HELIX) { + bool un = (g->meshCombine == Group::CombineAs::UNION); + bool diff = (g->meshCombine == Group::CombineAs::DIFFERENCE); + bool intr = (g->meshCombine == Group::CombineAs::INTERSECTION); + bool asy = (g->meshCombine == Group::CombineAs::ASSEMBLE); Printf(false, " %Ftsolid model as"); Printf(false, "%Ba %f%D%Lc%Fd%s union%E " - "%f%D%Lc%Fd%s difference%E " - "%f%D%Lc%Fd%s%s%E ", + "%f%D%Lc%Fd%s assemble%E ", &TextWindow::ScreenChangeGroupOption, - Group::COMBINE_AS_UNION, + Group::CombineAs::UNION, un ? RADIO_TRUE : RADIO_FALSE, &TextWindow::ScreenChangeGroupOption, - Group::COMBINE_AS_DIFFERENCE, + Group::CombineAs::ASSEMBLE, + (asy ? RADIO_TRUE : RADIO_FALSE)); + Printf(false, "%Ba %f%D%Lc%Fd%s difference%E " + "%f%D%Lc%Fd%s intersection%E ", + &TextWindow::ScreenChangeGroupOption, + Group::CombineAs::DIFFERENCE, diff ? RADIO_TRUE : RADIO_FALSE, &TextWindow::ScreenChangeGroupOption, - Group::COMBINE_AS_ASSEMBLE, - asa ? (asy ? RADIO_TRUE : RADIO_FALSE) : " ", - asa ? " assemble" : ""); + Group::CombineAs::INTERSECTION, + intr ? RADIO_TRUE : RADIO_FALSE); - if(g->type == Group::EXTRUDE || - g->type == Group::LATHE) - { + if(g->type == Group::Type::EXTRUDE || g->type == Group::Type::LATHE || + g->type == Group::Type::REVOLVE || g->type == Group::Type::HELIX) { Printf(false, "%Bd %Ftcolor %E%Bz %Bd (%@, %@, %@) %f%D%Lf%Fl[change]%E", &g->color, @@ -380,7 +434,11 @@ void TextWindow::ShowGroupInfo(void) { Printf(false, "%Bd %Ftopacity%E %@ %f%Lf%Fl[change]%E", g->color.alphaF(), &TextWindow::ScreenOpacity); - } else if(g->type == Group::LINKED) { + } + + if(g->type == Group::Type::EXTRUDE || g->type == Group::Type::LATHE || + g->type == Group::Type::REVOLVE || g->type == Group::Type::LINKED || + g->type == Group::Type::HELIX) { Printf(false, " %Fd%f%LP%s suppress this group's solid model", &TextWindow::ScreenChangeGroupOption, g->suppress ? CHECK_TRUE : CHECK_FALSE); @@ -393,8 +451,7 @@ void TextWindow::ShowGroupInfo(void) { &TextWindow::ScreenChangeGroupOption, g->visible ? CHECK_TRUE : CHECK_FALSE); - Group *pg; pg = g->PreviousGroup(); - if(pg && pg->runningMesh.IsEmpty() && g->thisMesh.IsEmpty()) { + if(!g->IsForcedToMeshBySource()) { Printf(false, " %f%Lf%Fd%s force NURBS surfaces to triangle mesh", &TextWindow::ScreenChangeGroupOption, g->forceToMesh ? CHECK_TRUE : CHECK_FALSE); @@ -425,16 +482,17 @@ list_items: Printf(false, ""); Printf(false, "%Ft requests in group"); - int i, a = 0; - for(i = 0; i < SK.request.n; i++) { - Request *r = &(SK.request.elem[i]); + int a = 0; + for(auto &r : SK.request) { - if(r->group.v == shown.group.v) { - std::string s = r->DescriptionString(); + if(r.group == shown.group) { + std::string s = r.DescriptionString(); Printf(false, "%Bp %Fl%Ll%D%f%h%s%E", - (a & 1) ? 'd' : 'a', - r->h.v, (&TextWindow::ScreenSelectRequest), - &(TextWindow::ScreenHoverRequest), s.c_str()); + (a & 1) ? 'd' : 'a', + r.h.v, + (&TextWindow::ScreenSelectRequest), + &(TextWindow::ScreenHoverRequest), + s.c_str()); a++; } } @@ -443,16 +501,17 @@ list_items: a = 0; Printf(false, ""); Printf(false, "%Ft constraints in group (%d DOF)", g->solved.dof); - for(i = 0; i < SK.constraint.n; i++) { - Constraint *c = &(SK.constraint.elem[i]); + for(auto &c : SK.constraint) { - if(c->group.v == shown.group.v) { - std::string s = c->DescriptionString(); + if(c.group == shown.group) { + std::string s = c.DescriptionString(); Printf(false, "%Bp %Fl%Ll%D%f%h%s%E %s", - (a & 1) ? 'd' : 'a', - c->h.v, (&TextWindow::ScreenSelectConstraint), - (&TextWindow::ScreenHoverConstraint), s.c_str(), - c->reference ? "(ref)" : ""); + (a & 1) ? 'd' : 'a', + c.h.v, + (&TextWindow::ScreenSelectConstraint), + (&TextWindow::ScreenHoverConstraint), + s.c_str(), + c.reference ? "(ref)" : ""); a++; } } @@ -469,44 +528,46 @@ void TextWindow::ScreenAllowRedundant(int link, uint32_t v) { Group *g = SK.GetGroup(SS.TW.shown.group); g->allowRedundant = true; - SS.GenerateAll(); + SS.MarkGroupDirty(SS.TW.shown.group); - SS.TW.shown.screen = SCREEN_GROUP_INFO; + SS.TW.shown.screen = Screen::GROUP_INFO; SS.TW.Show(); } -void TextWindow::ShowGroupSolveInfo(void) { +void TextWindow::ShowGroupSolveInfo() { Group *g = SK.GetGroup(shown.group); if(g->IsSolvedOkay()) { // Go back to the default group info screen - shown.screen = SCREEN_GROUP_INFO; + shown.screen = Screen::GROUP_INFO; Show(); return; } Printf(true, "%FtGROUP %E%s", g->DescriptionString().c_str()); switch(g->solved.how) { - case System::DIDNT_CONVERGE: + case SolveResult::DIDNT_CONVERGE: Printf(true, "%FxSOLVE FAILED!%Fd unsolvable constraints"); Printf(true, "the following constraints are incompatible"); break; - case System::REDUNDANT_DIDNT_CONVERGE: + case SolveResult::REDUNDANT_DIDNT_CONVERGE: Printf(true, "%FxSOLVE FAILED!%Fd unsolvable constraints"); Printf(true, "the following constraints are unsatisfied"); break; - case System::REDUNDANT_OKAY: + case SolveResult::REDUNDANT_OKAY: Printf(true, "%FxSOLVE FAILED!%Fd redundant constraints"); Printf(true, "remove any one of these to fix it"); break; - case System::TOO_MANY_UNKNOWNS: + case SolveResult::TOO_MANY_UNKNOWNS: Printf(true, "Too many unknowns in a single group!"); return; + + default: ssassert(false, "Unexpected solve result"); } for(int i = 0; i < g->solved.remove.n; i++) { - hConstraint hc = g->solved.remove.elem[i]; + hConstraint hc = g->solved.remove[i]; Constraint *c = SK.constraint.FindByIdNoOops(hc); if(!c) continue; @@ -517,10 +578,15 @@ void TextWindow::ShowGroupSolveInfo(void) { c->DescriptionString().c_str()); } + if(g->solved.timeout) { + Printf(true, "%FxSome items in list have been ommitted%Fd"); + Printf(false, "%Fxbecause the operation timed out.%Fd"); + } + Printf(true, "It may be possible to fix the problem "); Printf(false, "by selecting Edit -> Undo."); - if(g->solved.how == System::REDUNDANT_OKAY) { + if(g->solved.how == SolveResult::REDUNDANT_OKAY) { Printf(true, "It is possible to suppress this error "); Printf(false, "by %Fl%f%Llallowing redundant constraints%E in ", &TextWindow::ScreenAllowRedundant); @@ -534,62 +600,81 @@ void TextWindow::ShowGroupSolveInfo(void) { // time. //----------------------------------------------------------------------------- void TextWindow::ScreenStepDimFinish(int link, uint32_t v) { - SS.TW.edit.meaning = EDIT_STEP_DIM_FINISH; + SS.TW.edit.meaning = Edit::STEP_DIM_FINISH; std::string edit_value; - if(SS.TW.shown.dimIsDistance) { - edit_value = SS.MmToString(SS.TW.shown.dimFinish); + if(SS.TW.stepDim.isDistance) { + edit_value = SS.MmToString(SS.TW.stepDim.finish); } else { - edit_value = ssprintf("%.3f", SS.TW.shown.dimFinish); + edit_value = ssprintf("%.3f", SS.TW.stepDim.finish); } SS.TW.ShowEditControl(12, edit_value); } void TextWindow::ScreenStepDimSteps(int link, uint32_t v) { - SS.TW.edit.meaning = EDIT_STEP_DIM_STEPS; - SS.TW.ShowEditControl(12, ssprintf("%d", SS.TW.shown.dimSteps)); + SS.TW.edit.meaning = Edit::STEP_DIM_STEPS; + SS.TW.ShowEditControl(12, ssprintf("%d", SS.TW.stepDim.steps)); } void TextWindow::ScreenStepDimGo(int link, uint32_t v) { hConstraint hc = SS.TW.shown.constraint; Constraint *c = SK.constraint.FindByIdNoOops(hc); if(c) { SS.UndoRemember(); - double start = c->valA, finish = SS.TW.shown.dimFinish; - int i, n = SS.TW.shown.dimSteps; - for(i = 1; i <= n; i++) { - c = SK.GetConstraint(hc); - c->valA = start + ((finish - start)*i)/n; - SS.MarkGroupDirty(c->group); - SS.GenerateAll(); - if(!SS.ActiveGroupsOkay()) { - // Failed to solve, so quit - break; - } - PaintGraphics(); + + double start = c->valA, finish = SS.TW.stepDim.finish; + SS.TW.stepDim.time = GetMilliseconds(); + SS.TW.stepDim.step = 1; + + if(!SS.TW.stepDim.timer) { + SS.TW.stepDim.timer = Platform::CreateTimer(); } + SS.TW.stepDim.timer->onTimeout = [=] { + if(SS.TW.stepDim.step <= SS.TW.stepDim.steps) { + c->valA = start + ((finish - start)*SS.TW.stepDim.step)/SS.TW.stepDim.steps; + SS.MarkGroupDirty(c->group); + SS.GenerateAll(); + if(!SS.ActiveGroupsOkay()) { + // Failed to solve, so quit + return; + } + SS.TW.stepDim.step++; + + const int64_t STEP_MILLIS = 50; + int64_t time = GetMilliseconds(); + if(time - SS.TW.stepDim.time < STEP_MILLIS) { + SS.TW.stepDim.timer->RunAfterNextFrame(); + } else { + SS.TW.stepDim.timer->RunAfter((unsigned)(time - SS.TW.stepDim.time - STEP_MILLIS)); + } + SS.TW.stepDim.time = time; + } else { + SS.TW.GoToScreen(Screen::LIST_OF_GROUPS); + SS.ScheduleShowTW(); + } + SS.GW.Invalidate(); + }; + SS.TW.stepDim.timer->RunAfterNextFrame(); } - InvalidateGraphics(); - SS.TW.GoToScreen(SCREEN_LIST_OF_GROUPS); } -void TextWindow::ShowStepDimension(void) { +void TextWindow::ShowStepDimension() { Constraint *c = SK.constraint.FindByIdNoOops(shown.constraint); if(!c) { - shown.screen = SCREEN_LIST_OF_GROUPS; + shown.screen = Screen::LIST_OF_GROUPS; Show(); return; } Printf(true, "%FtSTEP DIMENSION%E %s", c->DescriptionString().c_str()); - if(shown.dimIsDistance) { + if(stepDim.isDistance) { Printf(true, "%Ba %Ftstart%E %s", SS.MmToString(c->valA).c_str()); Printf(false, "%Bd %Ftfinish%E %s %Fl%Ll%f[change]%E", - SS.MmToString(shown.dimFinish).c_str(), &ScreenStepDimFinish); + SS.MmToString(stepDim.finish).c_str(), &ScreenStepDimFinish); } else { Printf(true, "%Ba %Ftstart%E %@", c->valA); Printf(false, "%Bd %Ftfinish%E %@ %Fl%Ll%f[change]%E", - shown.dimFinish, &ScreenStepDimFinish); + stepDim.finish, &ScreenStepDimFinish); } Printf(false, "%Ba %Ftsteps%E %d %Fl%Ll%f%D[change]%E", - shown.dimSteps, &ScreenStepDimSteps); + stepDim.steps, &ScreenStepDimSteps); Printf(true, " %Fl%Ll%fstep dimension now%E", &ScreenStepDimGo); @@ -604,16 +689,16 @@ void TextWindow::ShowStepDimension(void) { void TextWindow::ScreenChangeTangentArc(int link, uint32_t v) { switch(link) { case 'r': { - SS.TW.edit.meaning = EDIT_TANGENT_ARC_RADIUS; + SS.TW.edit.meaning = Edit::TANGENT_ARC_RADIUS; SS.TW.ShowEditControl(3, SS.MmToString(SS.tangentArcRadius)); break; } case 'a': SS.tangentArcManual = !SS.tangentArcManual; break; - case 'd': SS.tangentArcDeleteOld = !SS.tangentArcDeleteOld; break; + case 'm': SS.tangentArcModify = !SS.tangentArcModify; break; } } -void TextWindow::ShowTangentArc(void) { +void TextWindow::ShowTangentArc() { Printf(true, "%FtTANGENT ARC PARAMETERS%E"); Printf(true, "%Ft radius of created arc%E"); @@ -629,9 +714,9 @@ void TextWindow::ShowTangentArc(void) { Printf(false, " %Fd%f%La%s choose radius automatically%E", &ScreenChangeTangentArc, !SS.tangentArcManual ? CHECK_TRUE : CHECK_FALSE); - Printf(false, " %Fd%f%Ld%s delete original entities afterward%E", + Printf(false, " %Fd%f%Lm%s modify original entities%E", &ScreenChangeTangentArc, - SS.tangentArcDeleteOld ? CHECK_TRUE : CHECK_FALSE); + SS.tangentArcModify ? CHECK_TRUE : CHECK_FALSE); Printf(false, ""); Printf(false, "To create a tangent arc at a point,"); @@ -643,52 +728,46 @@ void TextWindow::ShowTangentArc(void) { //----------------------------------------------------------------------------- // The edit control is visible, and the user just pressed enter. //----------------------------------------------------------------------------- -void TextWindow::EditControlDone(const char *s) { +void TextWindow::EditControlDone(std::string s) { edit.showAgain = false; switch(edit.meaning) { - case EDIT_TIMES_REPEATED: { - Expr *e = Expr::From(s, true); - if(e) { + case Edit::TIMES_REPEATED: + if(Expr *e = Expr::From(s, /*popUpError=*/true)) { SS.UndoRemember(); double ev = e->Eval(); if((int)ev < 1) { - Error("Can't repeat fewer than 1 time."); + Error(_("Can't repeat fewer than 1 time.")); break; } if((int)ev > 999) { - Error("Can't repeat more than 999 times."); + Error(_("Can't repeat more than 999 times.")); break; } Group *g = SK.GetGroup(edit.group); g->valA = ev; - if(g->type == Group::ROTATE) { - int i, c = 0; - for(i = 0; i < SK.constraint.n; i++) { - if(SK.constraint.elem[i].group.v == g->h.v) c++; - } + if(g->type == Group::Type::ROTATE) { // If the group does not contain any constraints, then // set the numerical guess to space the copies uniformly // over one rotation. Don't touch the guess if we're // already constrained, because that would break // convergence. - if(c == 0) { + if(g->GetNumConstraints() == 0) { double copies = (g->skipFirst) ? (ev + 1) : ev; SK.GetParam(g->h.param(3))->val = PI/(2*copies); } } SS.MarkGroupDirty(g->h); - SS.ScheduleGenerateAll(); } break; - } - case EDIT_GROUP_NAME: { - if(!*s) { - Error("Group name cannot be empty"); + + case Edit::GROUP_NAME: + if(s.empty()) { + Error(_("Group name cannot be empty")); } else { SS.UndoRemember(); @@ -696,91 +775,82 @@ void TextWindow::EditControlDone(const char *s) { g->name = s; } break; - } - case EDIT_GROUP_SCALE: { - Expr *e = Expr::From(s, true); - if(e) { + + case Edit::GROUP_SCALE: + if(Expr *e = Expr::From(s, /*popUpError=*/true)) { double ev = e->Eval(); if(fabs(ev) < 1e-6) { - Error("Scale cannot be zero."); + Error(_("Scale cannot be zero.")); } else { Group *g = SK.GetGroup(edit.group); g->scale = ev; SS.MarkGroupDirty(g->h); - SS.ScheduleGenerateAll(); } } break; - } - case EDIT_GROUP_COLOR: { + + case Edit::GROUP_COLOR: { Vector rgb; - if(sscanf(s, "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) { + if(sscanf(s.c_str(), "%lf, %lf, %lf", &rgb.x, &rgb.y, &rgb.z)==3) { rgb = rgb.ClampWithin(0, 1); Group *g = SK.group.FindByIdNoOops(SS.TW.shown.group); if(!g) break; - g->color = RGBf(rgb.x, rgb.y, rgb.z); + g->color = RgbaColor::FromFloat((float)rgb.x, (float)rgb.y, (float)rgb.z, + g->color.alphaF()); SS.MarkGroupDirty(g->h); - SS.ScheduleGenerateAll(); SS.GW.ClearSuper(); } else { - Error("Bad format: specify color as r, g, b"); + Error(_("Bad format: specify color as r, g, b")); } break; } - case EDIT_GROUP_OPACITY: { - Expr *e = Expr::From(s, true); - if(e) { + case Edit::GROUP_OPACITY: + if(Expr *e = Expr::From(s, /*popUpError=*/true)) { double alpha = e->Eval(); if(alpha < 0 || alpha > 1) { - Error("Opacity must be between zero and one."); + Error(_("Opacity must be between zero and one.")); } else { Group *g = SK.GetGroup(edit.group); g->color.alpha = (int)(255.1f * alpha); SS.MarkGroupDirty(g->h); - SS.ScheduleGenerateAll(); SS.GW.ClearSuper(); } } break; - } - case EDIT_TTF_TEXT: { + + case Edit::TTF_TEXT: SS.UndoRemember(); - Request *r = SK.request.FindByIdNoOops(edit.request); - if(r) { + if(Request *r = SK.request.FindByIdNoOops(edit.request)) { r->str = s; SS.MarkGroupDirty(r->group); - SS.ScheduleGenerateAll(); } break; - } - case EDIT_STEP_DIM_FINISH: { - Expr *e = Expr::From(s, true); - if(!e) { - break; - } - if(shown.dimIsDistance) { - shown.dimFinish = SS.ExprToMm(e); - } else { - shown.dimFinish = e->Eval(); + + case Edit::STEP_DIM_FINISH: + if(Expr *e = Expr::From(s, /*popUpError=*/true)) { + if(stepDim.isDistance) { + stepDim.finish = SS.ExprToMm(e); + } else { + stepDim.finish = e->Eval(); + } } break; - } - case EDIT_STEP_DIM_STEPS: - shown.dimSteps = min(300, max(1, atoi(s))); + + case Edit::STEP_DIM_STEPS: + stepDim.steps = min(300, max(1, atoi(s.c_str()))); break; - case EDIT_TANGENT_ARC_RADIUS: { - Expr *e = Expr::From(s, true); - if(!e) break; - if(e->Eval() < LENGTH_EPS) { - Error("Radius cannot be zero or negative."); - break; + case Edit::TANGENT_ARC_RADIUS: + if(Expr *e = Expr::From(s, /*popUpError=*/true)) { + if(e->Eval() < LENGTH_EPS) { + Error(_("Radius cannot be zero or negative.")); + break; + } + SS.tangentArcRadius = SS.ExprToMm(e); } - SS.tangentArcRadius = SS.ExprToMm(e); break; - } default: { int cnt = 0; @@ -788,19 +858,16 @@ void TextWindow::EditControlDone(const char *s) { if(EditControlDoneForConfiguration(s)) cnt++; if(EditControlDoneForPaste(s)) cnt++; if(EditControlDoneForView(s)) cnt++; - if(cnt > 1) { - // The identifiers were somehow assigned not uniquely? - oops(); - } + ssassert(cnt == 1, "Expected exactly one parameter to be edited"); break; } } - InvalidateGraphics(); + SS.GW.Invalidate(); SS.ScheduleShowTW(); if(!edit.showAgain) { HideEditControl(); - edit.meaning = EDIT_NOTHING; + edit.meaning = Edit::NOTHING; } } diff --git a/src/textwin.cpp b/src/textwin.cpp index 656ac62..e243825 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -4,70 +4,286 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" -#include "generated/icons.h" +namespace SolveSpace { + +class Button { +public: + virtual std::string Tooltip() = 0; + virtual void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) = 0; + virtual int AdvanceWidth() = 0; + virtual void Click() = 0; +}; + +class SpacerButton : public Button { +public: + std::string Tooltip() override { return ""; } + + void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) override { + // Draw a darker-grey spacer in between the groups of icons. + uiCanvas->DrawRect(x, x + 4, y, y - 24, + /*fillColor=*/{ 45, 45, 45, 255 }, + /*outlineColor=*/{}); + } + + int AdvanceWidth() override { return 12; } + + void Click() override {} +}; + +class ShowHideButton : public Button { +public: + bool *variable; + std::string tooltip; + std::string iconName; + std::shared_ptr icon; + + ShowHideButton(bool *variable, std::string iconName, std::string tooltip) + : variable(variable), tooltip(tooltip), iconName(iconName) {} + + std::string Tooltip() override { + return ((*variable) ? "Hide " : "Show ") + tooltip; + } + + void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) override { + if(icon == NULL) { + icon = LoadPng("icons/text-window/" + iconName + ".png"); + } + + uiCanvas->DrawPixmap(icon, x, y - 24); + if(asHovered) { + uiCanvas->DrawRect(x - 2, x + 26, y + 2, y - 26, + /*fillColor=*/{ 255, 255, 0, 75 }, + /*outlineColor=*/{}); + } + if(!*(variable)) { + int s = 0, f = 24; + RgbaColor color = { 255, 0, 0, 150 }; + uiCanvas->DrawLine(x+s, y-s, x+f, y-f, color, 2); + uiCanvas->DrawLine(x+s, y-f, x+f, y-s, color, 2); + } + } + + int AdvanceWidth() override { return 32; } + + void Click() override { SS.GW.ToggleBool(variable); } +}; + +class FacesButton : public ShowHideButton { +public: + FacesButton() + : ShowHideButton(&(SS.GW.showFaces), "faces", "") {} + + std::string Tooltip() override { + if(*variable) { + return "Don't make faces selectable with mouse"; + } else { + return "Make faces selectable with mouse"; + } + } +}; + +class OccludedLinesButton : public Button { +public: + std::shared_ptr visibleIcon; + std::shared_ptr stippledIcon; + std::shared_ptr invisibleIcon; + + std::string Tooltip() override { + switch(SS.GW.drawOccludedAs) { + case GraphicsWindow::DrawOccludedAs::INVISIBLE: + return "Stipple occluded lines"; + + case GraphicsWindow::DrawOccludedAs::STIPPLED: + return "Draw occluded lines"; + + case GraphicsWindow::DrawOccludedAs::VISIBLE: + return "Don't draw occluded lines"; + + default: ssassert(false, "Unexpected mode"); + } + } + + void Draw(UiCanvas *uiCanvas, int x, int y, bool asHovered) override { + if(visibleIcon == NULL) { + visibleIcon = LoadPng("icons/text-window/occluded-visible.png"); + } + if(stippledIcon == NULL) { + stippledIcon = LoadPng("icons/text-window/occluded-stippled.png"); + } + if(invisibleIcon == NULL) { + invisibleIcon = LoadPng("icons/text-window/occluded-invisible.png"); + } + + std::shared_ptr icon; + switch(SS.GW.drawOccludedAs) { + case GraphicsWindow::DrawOccludedAs::INVISIBLE: icon = invisibleIcon; break; + case GraphicsWindow::DrawOccludedAs::STIPPLED: icon = stippledIcon; break; + case GraphicsWindow::DrawOccludedAs::VISIBLE: icon = visibleIcon; break; + } + + uiCanvas->DrawPixmap(icon, x, y - 24); + if(asHovered) { + uiCanvas->DrawRect(x - 2, x + 26, y + 2, y - 26, + /*fillColor=*/{ 255, 255, 0, 75 }, + /*outlineColor=*/{}); + } + } + + int AdvanceWidth() override { return 32; } + + void Click() override { + switch(SS.GW.drawOccludedAs) { + case GraphicsWindow::DrawOccludedAs::INVISIBLE: + SS.GW.drawOccludedAs = GraphicsWindow::DrawOccludedAs::STIPPLED; + break; + + case GraphicsWindow::DrawOccludedAs::STIPPLED: + SS.GW.drawOccludedAs = GraphicsWindow::DrawOccludedAs::VISIBLE; + break; + + case GraphicsWindow::DrawOccludedAs::VISIBLE: + SS.GW.drawOccludedAs = GraphicsWindow::DrawOccludedAs::INVISIBLE; + break; + } + + SS.GenerateAll(); + SS.GW.Invalidate(); + SS.ScheduleShowTW(); + } +}; + +static SpacerButton spacerButton; + +static ShowHideButton workplanesButton = + { &(SS.GW.showWorkplanes), "workplane", "workplanes from inactive groups" }; +static ShowHideButton normalsButton = + { &(SS.GW.showNormals), "normal", "normals" }; +static ShowHideButton pointsButton = + { &(SS.GW.showPoints), "point", "points" }; +static ShowHideButton constructionButton = + { &(SS.GW.showConstruction), "construction", "construction entities" }; +static ShowHideButton constraintsButton = + { &(SS.GW.showConstraints), "constraint", "constraints and dimensions" }; +static FacesButton facesButton; +static ShowHideButton shadedButton = + { &(SS.GW.showShaded), "shaded", "shaded view of solid model" }; +static ShowHideButton edgesButton = + { &(SS.GW.showEdges), "edges", "edges of solid model" }; +static ShowHideButton outlinesButton = + { &(SS.GW.showOutlines), "outlines", "outline of solid model" }; +static ShowHideButton meshButton = + { &(SS.GW.showMesh), "mesh", "triangle mesh of solid model" }; +static OccludedLinesButton occludedLinesButton; + +static Button *buttons[] = { + &workplanesButton, + &normalsButton, + &pointsButton, + &constructionButton, + &constraintsButton, + &facesButton, + &spacerButton, + &shadedButton, + &edgesButton, + &outlinesButton, + &meshButton, + &spacerButton, + &occludedLinesButton, +}; + +/** Foreground color codes. */ const TextWindow::Color TextWindow::fgColors[] = { - { 'd', RGBi(255, 255, 255) }, - { 'l', RGBi(100, 100, 255) }, - { 't', RGBi(255, 200, 0) }, + { 'd', RGBi(255, 255, 255) }, // Default : white + { 'l', RGBi(100, 200, 255) }, // links : blue + { 't', RGBi(255, 200, 100) }, // tree/text : yellow { 'h', RGBi( 90, 90, 90) }, - { 's', RGBi( 40, 255, 40) }, + { 's', RGBi( 40, 255, 40) }, // Ok : green { 'm', RGBi(200, 200, 0) }, - { 'r', RGBi( 0, 0, 0) }, - { 'x', RGBi(255, 20, 20) }, - { 'i', RGBi( 0, 255, 255) }, + { 'r', RGBi( 0, 0, 0) }, // Reverse : black + { 'x', RGBi(255, 20, 20) }, // Error : red + { 'i', RGBi( 0, 255, 255) }, // Info : cyan { 'g', RGBi(160, 160, 160) }, { 'b', RGBi(200, 200, 200) }, { 0, RGBi( 0, 0, 0) } }; +/** Background color codes. */ const TextWindow::Color TextWindow::bgColors[] = { - { 'd', RGBi( 0, 0, 0) }, + { 'd', RGBi( 0, 0, 0) }, // Default : black { 't', RGBi( 34, 15, 15) }, - { 'a', RGBi( 25, 25, 25) }, - { 'r', RGBi(255, 255, 255) }, + { 'a', RGBi( 25, 25, 25) }, // Alternate : dark gray + { 'r', RGBi(255, 255, 255) }, // Reverse : white { 0, RGBi( 0, 0, 0) } }; -bool TextWindow::SPACER = false; -TextWindow::HideShowIcon TextWindow::hideShowIcons[] = { - { &(SS.GW.showWorkplanes), Icon_workplane, "workplanes from inactive groups"}, - { &(SS.GW.showNormals), Icon_normal, "normals" }, - { &(SS.GW.showPoints), Icon_point, "points" }, - { &(SS.GW.showConstraints), Icon_constraint, "constraints and dimensions" }, - { &(SS.GW.showFaces), Icon_faces, "XXX - special cased" }, - { &SPACER, 0, 0 }, - { &(SS.GW.showShaded), Icon_shaded, "shaded view of solid model" }, - { &(SS.GW.showEdges), Icon_edges, "edges of solid model" }, - { &(SS.GW.showOutlines), Icon_outlines, "outline of solid model" }, - { &(SS.GW.showMesh), Icon_mesh, "triangle mesh of solid model" }, - { &SPACER, 0, 0 }, - { &(SS.GW.showHdnLines), Icon_hidden_lines, "hidden lines" }, - { 0, 0, 0 } -}; - void TextWindow::MakeColorTable(const Color *in, float *out) { int i; for(i = 0; in[i].c != 0; i++) { int c = in[i].c; - if(c < 0 || c > 255) oops(); + ssassert(c >= 0 && c <= 255, "Unexpected color index"); out[c*3 + 0] = in[i].color.redF(); out[c*3 + 1] = in[i].color.greenF(); out[c*3 + 2] = in[i].color.blueF(); } } -void TextWindow::Init(void) { +void TextWindow::Init() { + if(!window) { + window = Platform::CreateWindow(Platform::Window::Kind::TOOL, SS.GW.window); + if(window) { + canvas = CreateRenderer(); + + using namespace std::placeholders; + window->onClose = []() { + SS.GW.showTextWindow = false; + SS.GW.EnsureValidActives(); + }; + window->onMouseEvent = [this](Platform::MouseEvent event) { + using Platform::MouseEvent; + + if(event.type == MouseEvent::Type::PRESS || + event.type == MouseEvent::Type::DBL_PRESS || + event.type == MouseEvent::Type::MOTION) { + bool isClick = (event.type != MouseEvent::Type::MOTION); + bool leftDown = (event.button == MouseEvent::Button::LEFT); + this->MouseEvent(isClick, leftDown, event.x, event.y); + return true; + } else if(event.type == MouseEvent::Type::LEAVE) { + MouseLeave(); + return true; + } else if(event.type == MouseEvent::Type::SCROLL_VERT) { + ScrollbarEvent(window->GetScrollbarPosition() - + LINE_HEIGHT / 2 * event.scrollDelta); + } + return false; + }; + window->onKeyboardEvent = SS.GW.window->onKeyboardEvent; + window->onRender = std::bind(&TextWindow::Paint, this); + window->onEditingDone = std::bind(&TextWindow::EditControlDone, this, _1); + window->onScrollbarAdjusted = std::bind(&TextWindow::ScrollbarEvent, this, _1); + window->SetMinContentSize(370, 370); + } + } + ClearSuper(); } -void TextWindow::ClearSuper(void) { - HideEditControl(); +void TextWindow::ClearSuper() { + // Ugly hack, but not so ugly as the next line + Platform::WindowRef oldWindow = std::move(window); + std::shared_ptr oldCanvas = canvas; // Cannot use *this = {} here because TextWindow instances // are 2.4MB long; this causes stack overflows in prologue // when built with MSVC, even with optimizations. memset(this, 0, sizeof(*this)); + // Return old canvas + window = std::move(oldWindow); + canvas = oldCanvas; + + HideEditControl(); + MakeColorTable(fgColors, fgColorTable); MakeColorTable(bgColors, bgColorTable); @@ -75,9 +291,12 @@ void TextWindow::ClearSuper(void) { Show(); } -void TextWindow::HideEditControl(void) { +void TextWindow::HideEditControl() { editControl.colorPicker.show = false; - HideTextEditControl(); + if(window) { + window->HideEditor(); + window->Invalidate(); + } } void TextWindow::ShowEditControl(int col, const std::string &str, int halfRow) { @@ -85,14 +304,16 @@ void TextWindow::ShowEditControl(int col, const std::string &str, int halfRow) { editControl.halfRow = halfRow; editControl.col = col; - int x = LEFT_MARGIN + CHAR_WIDTH*col; + int x = LEFT_MARGIN + CHAR_WIDTH_*col; int y = (halfRow - SS.TW.scrollPos)*(LINE_HEIGHT/2); - ShowTextEditControl(x, y + 18, str); + double width, height; + window->GetContentSize(&width, &height); + window->ShowEditor(x, y + LINE_HEIGHT - 2, LINE_HEIGHT - 4, + width - x, /*isMonospace=*/true, str); } -void TextWindow::ShowEditControlWithColorPicker(int col, RgbaColor rgb) -{ +void TextWindow::ShowEditControlWithColorPicker(int col, RgbaColor rgb) { SS.ScheduleShowTW(); editControl.colorPicker.show = true; @@ -103,7 +324,7 @@ void TextWindow::ShowEditControlWithColorPicker(int col, RgbaColor rgb) ShowEditControl(col, ssprintf("%.2f, %.2f, %.2f", rgb.redF(), rgb.greenF(), rgb.blueF())); } -void TextWindow::ClearScreen(void) { +void TextWindow::ClearScreen() { int i, j; for(i = 0; i < MAX_ROWS; i++) { for(j = 0; j < MAX_COLS; j++) { @@ -117,12 +338,25 @@ void TextWindow::ClearScreen(void) { rows = 0; } +// This message was addded when someone had too many fonts for the text window +// Scrolling seemed to be broken, but was actaully at the MAX_ROWS. +static const char* endString = " **** End of Text Screen ****"; + void TextWindow::Printf(bool halfLine, const char *fmt, ...) { - va_list vl; - va_start(vl, fmt); + if(!canvas) return; if(rows >= MAX_ROWS) return; + if(rows >= MAX_ROWS-2 && (fmt != endString)) { + // twice due to some half-row issues on resizing + Printf(halfLine, endString); + Printf(halfLine, endString); + return; + } + + va_list vl; + va_start(vl, fmt); + int r, c; r = rows; top[r] = (r == 0) ? 0 : (top[r-1] + (halfLine ? 3 : 2)); @@ -255,7 +489,7 @@ void TextWindow::Printf(bool halfLine, const char *fmt, ...) { } for(utf8_iterator it(buf); *it; ++it) { - for(int i = 0; i < ssglBitmapCharWidth(*it); i++) { + for(size_t i = 0; i < canvas->GetBitmapFont()->GetWidth(*it); i++) { if(c >= MAX_COLS) goto done; text[r][c] = (i == 0) ? *it : ' '; meta[r][c].fg = fg; @@ -269,7 +503,9 @@ void TextWindow::Printf(bool halfLine, const char *fmt, ...) { } } - fmt++; + utf8_iterator it(fmt); + it++; + fmt = it.ptr(); } while(c < MAX_COLS) { meta[r][c].fg = fg; @@ -282,11 +518,13 @@ done: va_end(vl); } -#define gs (SS.GW.gs) -void TextWindow::Show(void) { - if(!(SS.GW.pending.operation)) SS.GW.ClearPending(); +void TextWindow::Show() { + if(SS.GW.pending.operation == GraphicsWindow::Pending::NONE) { + SS.GW.ClearPending(/*scheduleShowTW=*/false); + } SS.GW.GroupSelection(); + auto const &gs = SS.GW.gs; // Make sure these tests agree with test used to draw indicator line on // main list of groups screen. @@ -300,28 +538,28 @@ void TextWindow::Show(void) { Printf(true, "%Fl%f%Ll(cancel operation)%E", &TextWindow::ScreenUnselectAll); } else if((gs.n > 0 || gs.constraints > 0) && - shown.screen != SCREEN_PASTE_TRANSFORMED) + shown.screen != Screen::PASTE_TRANSFORMED) { - if(edit.meaning != EDIT_TTF_TEXT) HideEditControl(); + if(edit.meaning != Edit::TTF_TEXT) HideEditControl(); ShowHeader(false); DescribeSelection(); } else { - if(edit.meaning == EDIT_TTF_TEXT) HideEditControl(); + if(edit.meaning == Edit::TTF_TEXT) HideEditControl(); ShowHeader(true); switch(shown.screen) { default: - shown.screen = SCREEN_LIST_OF_GROUPS; + shown.screen = Screen::LIST_OF_GROUPS; // fall through - case SCREEN_LIST_OF_GROUPS: ShowListOfGroups(); break; - case SCREEN_GROUP_INFO: ShowGroupInfo(); break; - case SCREEN_GROUP_SOLVE_INFO: ShowGroupSolveInfo(); break; - case SCREEN_CONFIGURATION: ShowConfiguration(); break; - case SCREEN_STEP_DIMENSION: ShowStepDimension(); break; - case SCREEN_LIST_OF_STYLES: ShowListOfStyles(); break; - case SCREEN_STYLE_INFO: ShowStyleInfo(); break; - case SCREEN_PASTE_TRANSFORMED: ShowPasteTransformed(); break; - case SCREEN_EDIT_VIEW: ShowEditView(); break; - case SCREEN_TANGENT_ARC: ShowTangentArc(); break; + case Screen::LIST_OF_GROUPS: ShowListOfGroups(); break; + case Screen::GROUP_INFO: ShowGroupInfo(); break; + case Screen::GROUP_SOLVE_INFO: ShowGroupSolveInfo(); break; + case Screen::CONFIGURATION: ShowConfiguration(); break; + case Screen::STEP_DIMENSION: ShowStepDimension(); break; + case Screen::LIST_OF_STYLES: ShowListOfStyles(); break; + case Screen::STYLE_INFO: ShowStyleInfo(); break; + case Screen::PASTE_TRANSFORMED: ShowPasteTransformed(); break; + case Screen::EDIT_VIEW: ShowEditView(); break; + case Screen::TANGENT_ARC: ShowTangentArc(); break; } } Printf(false, ""); @@ -336,140 +574,70 @@ void TextWindow::Show(void) { } } - InvalidateText(); + if(window) Resize(); } -void TextWindow::TimerCallback(void) +void TextWindow::Resize() { - tooltippedIcon = hoveredIcon; - InvalidateText(); + double width, height; + window->GetContentSize(&width, &height); + + halfRows = (int)height / (LINE_HEIGHT/2); + + int bottom = top[rows-1] + 2; + scrollPos = min(scrollPos, bottom - halfRows); + scrollPos = max(scrollPos, 0); + + window->ConfigureScrollbar(0, top[rows - 1] + 1, halfRows); + window->SetScrollbarPosition(scrollPos); + window->SetScrollbarVisible(top[rows - 1] + 1 > halfRows); + window->Invalidate(); } -void TextWindow::DrawOrHitTestIcons(int how, double mx, double my) +void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow how, + double mx, double my) { - int width, height; - GetTextWindowSize(&width, &height); + double width, height; + window->GetContentSize(&width, &height); int x = 20, y = 33 + LINE_HEIGHT; y -= scrollPos*(LINE_HEIGHT/2); if(how == PAINT) { - double grey = 30.0/255; - double top = y - 28, bot = y + 4; - glColor4d(grey, grey, grey, 1.0); - ssglAxisAlignedQuad(0, width, top, bot); + int top = y - 28, bot = y + 4; + uiCanvas->DrawRect(0, (int)width, top, bot, + /*fillColor=*/{ 30, 30, 30, 255 }, /*outlineColor=*/{}); } - HideShowIcon *oldHovered = hoveredIcon; + Button *oldHovered = hoveredButton; if(how != PAINT) { - hoveredIcon = NULL; + hoveredButton = NULL; } - HideShowIcon *hsi; - for(hsi = &(hideShowIcons[0]); hsi->var; hsi++) { - if(hsi->var == &SPACER) { - // Draw a darker-grey spacer in between the groups of icons. - if(how == PAINT) { - int l = x, r = l + 4, - t = y, b = t - 24; - glColor4d(0.17, 0.17, 0.17, 1); - ssglAxisAlignedQuad(l, r, t, b); - } - x += 12; - continue; - } - + double hoveredX, hoveredY; + for(Button *button : buttons) { if(how == PAINT) { - glPushMatrix(); - glTranslated(x, y-24, 0); - // Only thing that matters about the color is the alpha, - // should be one for no transparency - glColor3d(0, 0, 0); - ssglDrawPixelsWithTexture(hsi->icon, 24, 24); - glPopMatrix(); - - if(hsi == hoveredIcon) { - glColor4d(1, 1, 0, 0.3); - ssglAxisAlignedQuad(x - 2, x + 26, y + 2, y - 26); - } - if(!*(hsi->var)) { - glColor4d(1, 0, 0, 0.6); - glLineWidth(2); - int s = 0, f = 24; - glBegin(GL_LINES); - glVertex2d(x+s, y-s); - glVertex2d(x+f, y-f); - glVertex2d(x+s, y-f); - glVertex2d(x+f, y-s); - glEnd(); - } - } else { - if(mx > x - 2 && mx < x + 26 && - my < y + 2 && my > y - 26) - { - // The mouse is hovered over this icon, so do the tooltip - // stuff. - if(hsi != tooltippedIcon) { - oldMousePos = Point2d::From(mx, my); - } - if(hsi != oldHovered || how == CLICK) { - SetTimerFor(1000); - } - hoveredIcon = hsi; - if(how == CLICK) { - SS.GW.ToggleBool(hsi->var); - } + button->Draw(uiCanvas, x, y, (button == hoveredButton)); + } else if(mx > x - 2 && mx < x + 26 && + my < y + 2 && my > y - 26) { + hoveredButton = button; + hoveredX = x - 2; + hoveredY = y - 26; + if(how == CLICK) { + button->Click(); } } - x += 32; + x += button->AdvanceWidth(); } - if(how != PAINT && hoveredIcon != oldHovered) { - InvalidateText(); - } - - if(tooltippedIcon) { - if(how == PAINT) { - std::string str; - - if(tooltippedIcon->icon == Icon_faces) { - if(SS.GW.showFaces) { - str = "Don't make faces selectable with mouse"; - } else { - str = "Make faces selectable with mouse"; - } - } else { - str = ssprintf("%s %s", *(tooltippedIcon->var) ? "Hide" : "Show", - tooltippedIcon->tip); - } - - double ox = oldMousePos.x, oy = oldMousePos.y - LINE_HEIGHT; - ox += 3; - oy -= 3; - int tw = (str.length() + 1)*(CHAR_WIDTH - 1); - ox = min(ox, (double) (width - 25) - tw); - oy = max(oy, 5.0); - - ssglInitializeBitmapFont(); - glLineWidth(1); - glColor4d(1.0, 1.0, 0.6, 1.0); - ssglAxisAlignedQuad(ox, ox+tw, oy, oy+LINE_HEIGHT); - glColor4d(0.0, 0.0, 0.0, 1.0); - ssglAxisAlignedLineLoop(ox, ox+tw, oy, oy+LINE_HEIGHT); - - glColor4d(0, 0, 0, 1); - ssglBitmapText(str, Vector::From(ox+5, oy-3+LINE_HEIGHT, 0)); + if(how != PAINT && hoveredButton != oldHovered) { + if(hoveredButton == NULL) { + window->SetTooltip("", 0, 0, 0, 0); } else { - if(!hoveredIcon || - (hoveredIcon != tooltippedIcon)) - { - tooltippedIcon = NULL; - InvalidateGraphics(); - } - // And if we're hovered, then we've set a timer that will cause - // us to show the tool tip later. + window->SetTooltip(hoveredButton->Tooltip(), hoveredX, hoveredY, 28, 28); } + window->Invalidate(); } } @@ -505,54 +673,48 @@ Vector TextWindow::HsvToRgb(Vector hsv) { return rgb; } -uint8_t *TextWindow::HsvPattern2d(void) { - static uint8_t Texture[256*256*3]; - static bool Init; - - if(!Init) { - int i, j, p; - p = 0; - for(i = 0; i < 256; i++) { - for(j = 0; j < 256; j++) { - Vector hsv = Vector::From(6.0*i/255.0, 1.0*j/255.0, 1); - Vector rgb = HsvToRgb(hsv); - rgb = rgb.ScaledBy(255); - Texture[p++] = (uint8_t)rgb.x; - Texture[p++] = (uint8_t)rgb.y; - Texture[p++] = (uint8_t)rgb.z; - } +std::shared_ptr TextWindow::HsvPattern2d(int w, int h) { + std::shared_ptr pixmap = Pixmap::Create(Pixmap::Format::RGB, w, h); + for(size_t j = 0; j < pixmap->height; j++) { + size_t p = pixmap->stride * j; + for(size_t i = 0; i < pixmap->width; i++) { + Vector hsv = Vector::From(6.0*i/(pixmap->width-1), 1.0*j/(pixmap->height-1), 1); + Vector rgb = HsvToRgb(hsv); + rgb = rgb.ScaledBy(255); + pixmap->data[p++] = (uint8_t)rgb.x; + pixmap->data[p++] = (uint8_t)rgb.y; + pixmap->data[p++] = (uint8_t)rgb.z; } - Init = true; } - return Texture; + return pixmap; } -uint8_t *TextWindow::HsvPattern1d(double h, double s) { - static uint8_t Texture[256*4]; - - int i, p; - p = 0; - for(i = 0; i < 256; i++) { - Vector hsv = Vector::From(6*h, s, 1.0*(255 - i)/255.0); - Vector rgb = HsvToRgb(hsv); - rgb = rgb.ScaledBy(255); - Texture[p++] = (uint8_t)rgb.x; - Texture[p++] = (uint8_t)rgb.y; - Texture[p++] = (uint8_t)rgb.z; - // Needs a padding byte, to make things four-aligned - p++; +std::shared_ptr TextWindow::HsvPattern1d(double hue, double sat, int w, int h) { + std::shared_ptr pixmap = Pixmap::Create(Pixmap::Format::RGB, w, h); + for(size_t i = 0; i < pixmap->height; i++) { + size_t p = i * pixmap->stride; + for(size_t j = 0; j < pixmap->width; j++) { + Vector hsv = Vector::From(6*hue, sat, 1.0*(pixmap->width - 1 - j)/pixmap->width); + Vector rgb = HsvToRgb(hsv); + rgb = rgb.ScaledBy(255); + pixmap->data[p++] = (uint8_t)rgb.x; + pixmap->data[p++] = (uint8_t)rgb.y; + pixmap->data[p++] = (uint8_t)rgb.z; + } } - return Texture; + return pixmap; } -void TextWindow::ColorPickerDone(void) { +void TextWindow::ColorPickerDone() { RgbaColor rgb = editControl.colorPicker.rgb; - EditControlDone(ssprintf("%.2f, %.2f, %.3f", rgb.redF(), rgb.greenF(), rgb.blueF()).c_str()); + EditControlDone(ssprintf("%.2f, %.2f, %.3f", rgb.redF(), rgb.greenF(), rgb.blueF())); } -bool TextWindow::DrawOrHitTestColorPicker(int how, bool leftDown, +bool TextWindow::DrawOrHitTestColorPicker(UiCanvas *uiCanvas, DrawOrHitHow how, bool leftDown, double x, double y) { + using Platform::Window; + bool mousePointerAsHand = false; if(how == HOVER && !leftDown) { @@ -561,7 +723,7 @@ bool TextWindow::DrawOrHitTestColorPicker(int how, bool leftDown, } if(!editControl.colorPicker.show) return false; - if(how == CLICK || (how == HOVER && leftDown)) InvalidateText(); + if(how == CLICK || (how == HOVER && leftDown)) window->Invalidate(); static const RgbaColor BaseColor[12] = { RGBi(255, 0, 0), @@ -580,10 +742,10 @@ bool TextWindow::DrawOrHitTestColorPicker(int how, bool leftDown, RGBi( 0, 127, 255), }; - int width, height; - GetTextWindowSize(&width, &height); + double width, height; + window->GetContentSize(&width, &height); - int px = LEFT_MARGIN + CHAR_WIDTH*editControl.col; + int px = LEFT_MARGIN + CHAR_WIDTH_*editControl.col; int py = (editControl.halfRow - SS.TW.scrollPos)*(LINE_HEIGHT/2); py += LINE_HEIGHT + 5; @@ -591,17 +753,21 @@ bool TextWindow::DrawOrHitTestColorPicker(int how, bool leftDown, static const int WIDTH = 16, HEIGHT = 12; static const int PITCH = 18, SIZE = 15; - px = min(px, width - (WIDTH*PITCH + 40)); + px = min(px, (int)width - (WIDTH*PITCH + 40)); int pxm = px + WIDTH*PITCH + 11, pym = py + HEIGHT*PITCH + 7; int bw = 6; if(how == PAINT) { - glColor4d(0.2, 0.2, 0.2, 1); - ssglAxisAlignedQuad(px, pxm+bw, py, pym+bw); - glColor4d(0.0, 0.0, 0.0, 1); - ssglAxisAlignedQuad(px+(bw/2), pxm+(bw/2), py+(bw/2), pym+(bw/2)); + uiCanvas->DrawRect(px, pxm+bw, py, pym+bw, + /*fillColor=*/{ 50, 50, 50, 255 }, + /*outlineColor=*/{}, + /*zIndex=*/1); + uiCanvas->DrawRect(px+(bw/2), pxm+(bw/2), py+(bw/2), pym+(bw/2), + /*fillColor=*/{ 0, 0, 0, 255 }, + /*outlineColor=*/{}, + /*zIndex=*/1); } else { if(x < px || x > pxm+(bw/2) || y < py || y > pym+(bw/2)) @@ -640,8 +806,10 @@ bool TextWindow::DrawOrHitTestColorPicker(int how, bool leftDown, int sx = px + 5 + PITCH*(i + 8) + 4, sy = py + 5 + PITCH*j; if(how == PAINT) { - glColor4d(CO(rgb), 1); - ssglAxisAlignedQuad(sx, sx+SIZE, sy, sy+SIZE); + uiCanvas->DrawRect(sx, sx+SIZE, sy, sy+SIZE, + /*fillColor=*/RGBf(rgb.x, rgb.y, rgb.z), + /*outlineColor=*/{}, + /*zIndex=*/2); } else if(how == CLICK) { if(x >= sx && x <= sx+SIZE && y >= sy && y <= sy+SIZE) { editControl.colorPicker.rgb = RGBf(rgb.x, rgb.y, rgb.z); @@ -660,8 +828,10 @@ bool TextWindow::DrawOrHitTestColorPicker(int how, bool leftDown, hxm = hx + PITCH*7 + SIZE; hym = hy + PITCH*2 + SIZE; if(how == PAINT) { - ssglColorRGB(editControl.colorPicker.rgb); - ssglAxisAlignedQuad(hx, hxm, hy, hym); + uiCanvas->DrawRect(hx, hxm, hy, hym, + /*fillColor=*/editControl.colorPicker.rgb, + /*outlineColor=*/{}, + /*zIndex=*/2); } else if(how == CLICK) { if(x >= hx && x <= hxm && y >= hy && y <= hym) { ColorPickerDone(); @@ -678,41 +848,16 @@ bool TextWindow::DrawOrHitTestColorPicker(int how, bool leftDown, hym = hy + PITCH*1 + SIZE; // The one-dimensional thing to pick the color's value if(how == PAINT) { - glBindTexture(GL_TEXTURE_2D, TEXTURE_COLOR_PICKER_1D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 256, 0, - GL_RGB, GL_UNSIGNED_BYTE, - HsvPattern1d(editControl.colorPicker.h, - editControl.colorPicker.s)); - - glEnable(GL_TEXTURE_2D); - glBegin(GL_QUADS); - glTexCoord2d(0, 0); - glVertex2d(hx, hy); - - glTexCoord2d(1, 0); - glVertex2d(hx, hym); - - glTexCoord2d(1, 1); - glVertex2d(hxm, hym); - - glTexCoord2d(0, 1); - glVertex2d(hxm, hy); - glEnd(); - glDisable(GL_TEXTURE_2D); - - double cx = hx+(hxm-hx)*(1 - editControl.colorPicker.v); - glColor4d(0, 0, 0, 1); - glLineWidth(1); - glBegin(GL_LINES); - glVertex2d(cx, hy); - glVertex2d(cx, hym); - glEnd(); + uiCanvas->DrawPixmap(HsvPattern1d(editControl.colorPicker.h, + editControl.colorPicker.s, + hxm-hx, hym-hy), + hx, hy, /*zIndex=*/2); + + int cx = hx+(int)((hxm-hx)*(1.0 - editControl.colorPicker.v)); + uiCanvas->DrawLine(cx, hy, cx, hym, + /*fillColor=*/{ 0, 0, 0, 255 }, + /*outlineColor=*/{}, + /*zIndex=*/3); } else if(how == CLICK || (how == HOVER && leftDown && editControl.colorPicker.picker1dActive)) { @@ -735,42 +880,19 @@ bool TextWindow::DrawOrHitTestColorPicker(int how, bool leftDown, hym = hy + PITCH*6 + SIZE; // Two-dimensional thing to pick a color by hue and saturation if(how == PAINT) { - glBindTexture(GL_TEXTURE_2D, TEXTURE_COLOR_PICKER_2D); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256, 0, - GL_RGB, GL_UNSIGNED_BYTE, HsvPattern2d()); - - glEnable(GL_TEXTURE_2D); - glBegin(GL_QUADS); - glTexCoord2d(0, 0); - glVertex2d(hx, hy); - - glTexCoord2d(1, 0); - glVertex2d(hx, hym); - - glTexCoord2d(1, 1); - glVertex2d(hxm, hym); - - glTexCoord2d(0, 1); - glVertex2d(hxm, hy); - glEnd(); - glDisable(GL_TEXTURE_2D); - - glColor4d(1, 1, 1, 1); - glLineWidth(1); - double cx = hx+(hxm-hx)*editControl.colorPicker.h, - cy = hy+(hym-hy)*editControl.colorPicker.s; - glBegin(GL_LINES); - glVertex2d(cx - 5, cy); - glVertex2d(cx + 4, cy); - glVertex2d(cx, cy - 5); - glVertex2d(cx, cy + 4); - glEnd(); + uiCanvas->DrawPixmap(HsvPattern2d(hxm-hx, hym-hy), hx, hy, + /*zIndex=*/2); + + int cx = hx+(int)((hxm-hx)*editControl.colorPicker.h), + cy = hy+(int)((hym-hy)*editControl.colorPicker.s); + uiCanvas->DrawLine(cx - 5, cy, cx + 5, cy, + /*fillColor=*/{ 255, 255, 255, 255 }, + /*outlineColor=*/{}, + /*zIndex=*/3); + uiCanvas->DrawLine(cx, cy - 5, cx, cy + 5, + /*fillColor=*/{ 255, 255, 255, 255 }, + /*outlineColor=*/{}, + /*zIndex=*/3); } else if(how == CLICK || (how == HOVER && leftDown && editControl.colorPicker.picker2dActive)) { @@ -790,88 +912,81 @@ bool TextWindow::DrawOrHitTestColorPicker(int how, bool leftDown, } } - SetMousePointerToHand(mousePointerAsHand); + window->SetCursor(mousePointerAsHand ? + Window::Cursor::HAND : + Window::Cursor::POINTER); return true; } -void TextWindow::Paint(void) { - int width, height; - GetTextWindowSize(&width, &height); - - // We would like things pixel-exact, to avoid shimmering. - glViewport(0, 0, width, height); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glColor3d(1, 1, 1); +void TextWindow::Paint() { + if (!canvas) return; - glTranslated(-1, 1, 0); - glScaled(2.0/width, -2.0/height, 1); - // Make things round consistently, avoiding exact integer boundary - glTranslated(-0.1, -0.1, 0); + double width, height; + window->GetContentSize(&width, &height); + if(halfRows != (int)height / (LINE_HEIGHT/2)) + Resize(); - halfRows = height / (LINE_HEIGHT/2); + Camera camera = {}; + camera.width = width; + camera.height = height; + camera.pixelRatio = window->GetDevicePixelRatio(); + camera.gridFit = (window->GetDevicePixelRatio() == 1); + camera.LoadIdentity(); + camera.offset.x = -camera.width / 2.0; + camera.offset.y = -camera.height / 2.0; - int bottom = top[rows-1] + 2; - scrollPos = min(scrollPos, bottom - halfRows); - scrollPos = max(scrollPos, 0); + Lighting lighting = {}; + lighting.backgroundColor = RGBi(0, 0, 0); - // Let's set up the scroll bar first - MoveTextScrollbarTo(scrollPos, top[rows - 1] + 1, halfRows); + canvas->SetLighting(lighting); + canvas->SetCamera(camera); + canvas->StartFrame(); - // Create the bitmap font that we're going to use. - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_BLEND); + UiCanvas uiCanvas = {}; + uiCanvas.canvas = canvas; + uiCanvas.flip = true; - // Now paint the window. int r, c, a; for(a = 0; a < 2; a++) { - if(a == 0) { - glBegin(GL_QUADS); - } else if(a == 1) { - glEnable(GL_TEXTURE_2D); - ssglInitializeBitmapFont(); - glBegin(GL_QUADS); - } - for(r = 0; r < rows; r++) { int ltop = top[r]; if(ltop < (scrollPos-1)) continue; if(ltop > scrollPos+halfRows) break; - for(c = 0; c < min((width/CHAR_WIDTH)+1, (int) MAX_COLS); c++) { - int x = LEFT_MARGIN + c*CHAR_WIDTH; + for(c = 0; c < min(((int)width/CHAR_WIDTH_)+1, (int) MAX_COLS); c++) { + int x = LEFT_MARGIN + c*CHAR_WIDTH_; int y = (ltop-scrollPos)*(LINE_HEIGHT/2) + 4; int fg = meta[r][c].fg; int bg = meta[r][c].bg; - RgbaColor bgRgb = meta[r][c].bgRgb; // On the first pass, all the background quads; on the next // pass, all the foreground (i.e., font) quads. if(a == 0) { - int bh = LINE_HEIGHT, adj = -2; + RgbaColor bgRgb = meta[r][c].bgRgb; + int bh = LINE_HEIGHT, adj = 0; if(bg == 'z') { - glColor3f(bgRgb.redF(), bgRgb.greenF(), bgRgb.blueF()); bh = CHAR_HEIGHT; adj += 2; } else { - glColor3fv(&(bgColorTable[bg*3])); + bgRgb = RgbaColor::FromFloat(bgColorTable[bg*3+0], + bgColorTable[bg*3+1], + bgColorTable[bg*3+2]); } if(bg != 'd') { // Move the quad down a bit, so that the descenders // still have the correct background. - y += adj; - ssglAxisAlignedQuad(x, x + CHAR_WIDTH, y, y + bh, false); - y -= adj; + uiCanvas.DrawRect(x, x + CHAR_WIDTH_, y + adj, y + adj + bh, + /*fillColor=*/bgRgb, /*outlineColor=*/{}); } } else if(a == 1) { - glColor3fv(&(fgColorTable[fg*3])); - ssglBitmapCharQuad(text[r][c], x, y + CHAR_HEIGHT); + RgbaColor fgRgb = RgbaColor::FromFloat(fgColorTable[fg*3+0], + fgColorTable[fg*3+1], + fgColorTable[fg*3+2]); + if(text[r][c] != ' ') { + uiCanvas.DrawBitmapChar(text[r][c], x, y + CHAR_HEIGHT, fgRgb); + } // If this is a link and it's hovered, then draw the // underline @@ -902,73 +1017,68 @@ void TextWindow::Paint(void) { cs++; } - glEnd(); - // Always use the color of the rightmost character // in the link, so that underline is consistent color fg = meta[r][cf-1].fg; - glColor3fv(&(fgColorTable[fg*3])); - glDisable(GL_TEXTURE_2D); - glLineWidth(1); - glBegin(GL_LINES); - int yp = y + CHAR_HEIGHT; - glVertex2d(LEFT_MARGIN + cs*CHAR_WIDTH, yp); - glVertex2d(LEFT_MARGIN + cf*CHAR_WIDTH, yp); - glEnd(); - - glEnable(GL_TEXTURE_2D); - glBegin(GL_QUADS); + fgRgb = RgbaColor::FromFloat(fgColorTable[fg*3+0], + fgColorTable[fg*3+1], + fgColorTable[fg*3+2]); + int yp = y + CHAR_HEIGHT; + uiCanvas.DrawLine(LEFT_MARGIN + cs*CHAR_WIDTH_, yp, + LEFT_MARGIN + cf*CHAR_WIDTH_, yp, + fgRgb); } } } } - - glEnd(); - glDisable(GL_TEXTURE_2D); } // The line to indicate the column of radio buttons that indicates the // active group. SS.GW.GroupSelection(); + auto const &gs = SS.GW.gs; // Make sure this test agrees with test to determine which screen is drawn if(!SS.GW.pending.description && gs.n == 0 && gs.constraints == 0 && - shown.screen == SCREEN_LIST_OF_GROUPS) + shown.screen == Screen::LIST_OF_GROUPS) { int x = 29, y = 70 + LINE_HEIGHT; y -= scrollPos*(LINE_HEIGHT/2); - glLineWidth(1); - glColor3fv(&(fgColorTable['t'*3])); - glBegin(GL_LINES); - glVertex2d(x, y); - glVertex2d(x, y+40); - glEnd(); + RgbaColor color = RgbaColor::FromFloat(fgColorTable['t'*3+0], + fgColorTable['t'*3+1], + fgColorTable['t'*3+2]); + uiCanvas.DrawLine(x, y, x, y+40, color); } // The header has some icons that are drawn separately from the text - DrawOrHitTestIcons(PAINT, 0, 0); + DrawOrHitTestIcons(&uiCanvas, PAINT, 0, 0); // And we may show a color picker for certain editable fields - DrawOrHitTestColorPicker(PAINT, false, 0, 0); + DrawOrHitTestColorPicker(&uiCanvas, PAINT, false, 0, 0); + + canvas->FlushFrame(); + canvas->FinishFrame(); + canvas->Clear(); } void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) { - if(TextEditControlIsVisible() || GraphicsEditControlIsVisible()) { - if(DrawOrHitTestColorPicker(leftClick ? CLICK : HOVER, leftDown, x, y)) - { + using Platform::Window; + + if(SS.TW.window->IsEditorVisible() || SS.GW.window->IsEditorVisible()) { + if(DrawOrHitTestColorPicker(NULL, leftClick ? CLICK : HOVER, leftDown, x, y)) { return; } if(leftClick) { HideEditControl(); - HideGraphicsEditControl(); + SS.GW.window->HideEditor(); } else { - SetMousePointerToHand(false); + window->SetCursor(Window::Cursor::POINTER); } return; } - DrawOrHitTestIcons(leftClick ? CLICK : HOVER, x, y); + DrawOrHitTestIcons(NULL, leftClick ? CLICK : HOVER, x, y); GraphicsWindow::Selection ps = SS.GW.hover; SS.GW.hover.Clear(); @@ -979,7 +1089,7 @@ void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) { hoveredCol = 0; // Find the corresponding character in the text buffer - int c = (int)((x - LEFT_MARGIN) / CHAR_WIDTH); + int c = (int)((x - LEFT_MARGIN) / CHAR_WIDTH_); int hh = (LINE_HEIGHT)/2; y += scrollPos*hh; int r; @@ -988,63 +1098,62 @@ void TextWindow::MouseEvent(bool leftClick, bool leftDown, double x, double y) { break; } } - if(r < 0 || c < 0 || r >= rows || c >= MAX_COLS) { - SetMousePointerToHand(false); - goto done; - } + if(r >= 0 && c >= 0 && r < rows && c < MAX_COLS) { + window->SetCursor(Window::Cursor::POINTER); - hoveredRow = r; - hoveredCol = c; + hoveredRow = r; + hoveredCol = c; -#define META (meta[r][c]) - if(leftClick) { - if(META.link && META.f) { - (META.f)(META.link, META.data); - Show(); - InvalidateGraphics(); - } - } else { - if(META.link) { - SetMousePointerToHand(true); - if(META.h) { - (META.h)(META.link, META.data); + const auto &item = meta[r][c]; + if(leftClick) { + if(item.link && item.f) { + (item.f)(item.link, item.data); + Show(); + SS.GW.Invalidate(); } } else { - SetMousePointerToHand(false); + if(item.link) { + window->SetCursor(Window::Cursor::HAND); + if(item.h) { + (item.h)(item.link, item.data); + } + } else { + window->SetCursor(Window::Cursor::POINTER); + } } } -#undef META -done: if((!ps.Equals(&(SS.GW.hover))) || prevHoveredRow != hoveredRow || prevHoveredCol != hoveredCol) { - InvalidateGraphics(); - InvalidateText(); + SS.GW.Invalidate(); + window->Invalidate(); } } -void TextWindow::MouseLeave(void) { - tooltippedIcon = NULL; - hoveredIcon = NULL; +void TextWindow::MouseLeave() { + hoveredButton = NULL; hoveredRow = 0; hoveredCol = 0; - InvalidateText(); + window->Invalidate(); } -void TextWindow::ScrollbarEvent(int newPos) { - if(TextEditControlIsVisible()) +void TextWindow::ScrollbarEvent(double newPos) { + if(window->IsEditorVisible()) { + // An edit field is active. Do not move the scrollbar. return; + } int bottom = top[rows-1] + 2; - newPos = min(newPos, bottom - halfRows); - newPos = max(newPos, 0); + newPos = min((int)newPos, bottom - halfRows); + newPos = max((int)newPos, 0); if(newPos != scrollPos) { - scrollPos = newPos; - MoveTextScrollbarTo(scrollPos, top[rows - 1] + 1, halfRows); - InvalidateText(); + scrollPos = (int)newPos; + window->SetScrollbarPosition(scrollPos); + window->Invalidate(); } } +} diff --git a/src/toolbar.cpp b/src/toolbar.cpp index 77f795f..4d0b883 100644 --- a/src/toolbar.cpp +++ b/src/toolbar.cpp @@ -6,143 +6,176 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- #include "solvespace.h" -#include "generated/icons.h" - -static uint8_t SPACER[1]; -static const struct { - uint8_t *image; - int menu; - const char *tip; -} Toolbar[] = { - { Icon_line, GraphicsWindow::MNU_LINE_SEGMENT, "Sketch line segment" }, - { Icon_rectangle, GraphicsWindow::MNU_RECTANGLE, "Sketch rectangle" }, - { Icon_circle, GraphicsWindow::MNU_CIRCLE, "Sketch circle" }, - { Icon_arc, GraphicsWindow::MNU_ARC, "Sketch arc of a circle" }, - { Icon_text, GraphicsWindow::MNU_TTF_TEXT, "Sketch curves from text in a TrueType font" }, - { Icon_tangent_arc, GraphicsWindow::MNU_TANGENT_ARC, "Create tangent arc at selected point" }, - { Icon_bezier, GraphicsWindow::MNU_CUBIC, "Sketch cubic Bezier spline" }, - { Icon_point, GraphicsWindow::MNU_DATUM_POINT, "Sketch datum point" }, - { Icon_construction, GraphicsWindow::MNU_CONSTRUCTION, "Toggle construction" }, - { Icon_trim, GraphicsWindow::MNU_SPLIT_CURVES, "Split lines / curves where they intersect" }, - { SPACER, 0, 0 }, - - { Icon_length, GraphicsWindow::MNU_DISTANCE_DIA, "Constrain distance / diameter / length" }, - { Icon_angle, GraphicsWindow::MNU_ANGLE, "Constrain angle" }, - { Icon_horiz, GraphicsWindow::MNU_HORIZONTAL, "Constrain to be horizontal" }, - { Icon_vert, GraphicsWindow::MNU_VERTICAL, "Constrain to be vertical" }, - { Icon_parallel, GraphicsWindow::MNU_PARALLEL, "Constrain to be parallel or tangent" }, - { Icon_perpendicular, GraphicsWindow::MNU_PERPENDICULAR, "Constrain to be perpendicular" }, - { Icon_pointonx, GraphicsWindow::MNU_ON_ENTITY, "Constrain point on line / curve / plane / point" }, - { Icon_symmetric, GraphicsWindow::MNU_SYMMETRIC, "Constrain symmetric" }, - { Icon_equal, GraphicsWindow::MNU_EQUAL, "Constrain equal length / radius / angle" }, - { Icon_same_orientation,GraphicsWindow::MNU_ORIENTED_SAME, "Constrain normals in same orientation" }, - { Icon_other_supp, GraphicsWindow::MNU_OTHER_ANGLE, "Other supplementary angle" }, - { Icon_ref, GraphicsWindow::MNU_REFERENCE, "Toggle reference dimension" }, - { SPACER, 0, 0 }, - - { Icon_extrude, GraphicsWindow::MNU_GROUP_EXTRUDE, "New group extruding active sketch" }, - { Icon_lathe, GraphicsWindow::MNU_GROUP_LATHE, "New group rotating active sketch" }, - { Icon_step_rotate, GraphicsWindow::MNU_GROUP_ROT, "New group step and repeat rotating" }, - { Icon_step_translate, GraphicsWindow::MNU_GROUP_TRANS, "New group step and repeat translating" }, - { Icon_sketch_in_plane, GraphicsWindow::MNU_GROUP_WRKPL, "New group in new workplane (thru given entities)" }, - { Icon_sketch_in_3d, GraphicsWindow::MNU_GROUP_3D, "New group in 3d" }, - { Icon_assemble, GraphicsWindow::MNU_GROUP_LINK, "New group linking / assembling file" }, - { SPACER, 0, 0 }, - - { Icon_in3d, GraphicsWindow::MNU_NEAREST_ISO, "Nearest isometric view" }, - { Icon_ontoworkplane, GraphicsWindow::MNU_ONTO_WORKPLANE, "Align view to active workplane" }, - { NULL, 0, 0 } + +struct ToolIcon { + std::string name; + Command command; + const char *tooltip; + std::shared_ptr pixmap; +}; +static ToolIcon Toolbar[] = { + { "line", Command::LINE_SEGMENT, + N_("Sketch line segment"), {} }, + { "rectangle", Command::RECTANGLE, + N_("Sketch rectangle"), {} }, + { "circle", Command::CIRCLE, + N_("Sketch circle"), {} }, + { "arc", Command::ARC, + N_("Sketch arc of a circle"), {} }, + { "text", Command::TTF_TEXT, + N_("Sketch curves from text in a TrueType font"), {} }, + { "image", Command::IMAGE, + N_("Sketch image from a file"), {} }, + { "tangent-arc", Command::TANGENT_ARC, + N_("Create tangent arc at selected point"), {} }, + { "bezier", Command::CUBIC, + N_("Sketch cubic Bezier spline"), {} }, + { "point", Command::DATUM_POINT, + N_("Sketch datum point"), {} }, + { "construction", Command::CONSTRUCTION, + N_("Toggle construction"), {} }, + { "trim", Command::SPLIT_CURVES, + N_("Split lines / curves where they intersect"), {} }, + { "", Command::NONE, "", {} }, + + { "length", Command::DISTANCE_DIA, + N_("Constrain distance / diameter / length"), {} }, + { "angle", Command::ANGLE, + N_("Constrain angle"), {} }, + { "horiz", Command::HORIZONTAL, + N_("Constrain to be horizontal"), {} }, + { "vert", Command::VERTICAL, + N_("Constrain to be vertical"), {} }, + { "parallel", Command::PARALLEL, + N_("Constrain to be parallel or tangent"), {} }, + { "perpendicular", Command::PERPENDICULAR, + N_("Constrain to be perpendicular"), {} }, + { "pointonx", Command::ON_ENTITY, + N_("Constrain point on line / curve / plane / point"), {} }, + { "symmetric", Command::SYMMETRIC, + N_("Constrain symmetric"), {} }, + { "equal", Command::EQUAL, + N_("Constrain equal length / radius / angle"), {} }, + { "same-orientation",Command::ORIENTED_SAME, + N_("Constrain normals in same orientation"), {} }, + { "other-supp", Command::OTHER_ANGLE, + N_("Other supplementary angle"), {} }, + { "ref", Command::REFERENCE, + N_("Toggle reference dimension"), {} }, + { "", Command::NONE, "", {} }, + + { "extrude", Command::GROUP_EXTRUDE, + N_("New group extruding active sketch"), {} }, + { "lathe", Command::GROUP_LATHE, + N_("New group rotating active sketch"), {} }, + { "step-rotate", Command::GROUP_ROT, + N_("New group step and repeat rotating"), {} }, + { "step-translate", Command::GROUP_TRANS, + N_("New group step and repeat translating"), {} }, + { "sketch-in-plane", Command::GROUP_WRKPL, + N_("New group in new workplane (thru given entities)"), {} }, + { "sketch-in-3d", Command::GROUP_3D, + N_("New group in 3d"), {} }, + { "assemble", Command::GROUP_LINK, + N_("New group linking / assembling file"), {} }, + { "", Command::NONE, "", {} }, + + { "in3d", Command::NEAREST_ISO, + N_("Nearest isometric view"), {} }, + { "ontoworkplane", Command::ONTO_WORKPLANE, + N_("Align view to active workplane"), {} }, }; -void GraphicsWindow::ToolbarDraw(void) { - ToolbarDrawOrHitTest(0, 0, true, NULL); +void GraphicsWindow::ToolbarDraw(UiCanvas *canvas) { + ToolbarDrawOrHitTest(0, 0, canvas, NULL, NULL, NULL); } bool GraphicsWindow::ToolbarMouseMoved(int x, int y) { + double width, height; + window->GetContentSize(&width, &height); + x += ((int)width/2); y += ((int)height/2); - int nh = 0; - bool withinToolbar = ToolbarDrawOrHitTest(x, y, false, &nh); - if(!withinToolbar) nh = 0; + Command hitCommand; + int hitX, hitY; + bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hitCommand, &hitX, &hitY); - if(nh != toolbarTooltipped) { - // Don't let the tool tip move around if the mouse moves within the - // same item. - toolbarMouseX = x; - toolbarMouseY = y; - toolbarTooltipped = 0; + if(hitCommand != toolbarHovered) { + toolbarHovered = hitCommand; + Invalidate(); } - if(nh != toolbarHovered) { - toolbarHovered = nh; - SetTimerFor(1000); - PaintGraphics(); + if(toolbarHovered != Command::NONE) { + std::string tooltip; + for(ToolIcon &icon : Toolbar) { + if(toolbarHovered == icon.command) { + tooltip = Translate(icon.tooltip); + } + } + + Platform::KeyboardEvent accel = SS.GW.AcceleratorForCommand(toolbarHovered); + std::string accelDesc = Platform::AcceleratorDescription(accel); + if(!accelDesc.empty()) { + tooltip += ssprintf(" (%s)", accelDesc.c_str()); + } + + window->SetTooltip(tooltip, hitX, hitY, 32, 32); + } else { + window->SetTooltip("", 0, 0, 0, 0); } - // So if we moved off the toolbar, then toolbarHovered is now equal to - // zero, so it doesn't matter if the tool tip timer expires. And if - // we moved from one item to another, we reset the timer, so also okay. + return withinToolbar; } bool GraphicsWindow::ToolbarMouseDown(int x, int y) { + double width, height; + window->GetContentSize(&width, &height); + x += ((int)width/2); y += ((int)height/2); - int nh = -1; - bool withinToolbar = ToolbarDrawOrHitTest(x, y, false, &nh); - // They might have clicked within the toolbar, but not on a button. - if(withinToolbar && nh >= 0) { - for(int i = 0; SS.GW.menu[i].level >= 0; i++) { - if(nh == SS.GW.menu[i].id) { - (SS.GW.menu[i].fn)((GraphicsWindow::MenuId)SS.GW.menu[i].id); - break; - } - } + Command hitCommand; + bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hitCommand, NULL, NULL); + if(hitCommand != Command::NONE) { + SS.GW.ActivateCommand(hitCommand); } return withinToolbar; } -bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, - bool paint, int *menuHit) +bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, UiCanvas *canvas, + Command *hitCommand, int *hitX, int *hitY) { - int i; + double width, height; + window->GetContentSize(&width, &height); + int x = 17, y = (int)(height - 52); + // When changing these values, also change the asReference drawing code in drawentity.cpp. int fudge = 8; - int h = 32*16 + 3*16 + fudge; + int h = 34*16 + 3*16 + fudge; int aleft = 0, aright = 66, atop = y+16+fudge/2, abot = y+16-h; bool withinToolbar = (mx >= aleft && mx <= aright && my <= atop && my >= abot); - if(!paint && !withinToolbar) { + // Initialize/clear hitCommand. + if(hitCommand) *hitCommand = Command::NONE; + + if(!canvas && !withinToolbar) { // This gets called every MouseMove event, so return quickly. return false; } - if(paint) { - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glTranslated(-1, -1, 0); - glScaled(2.0/width, 2.0/height, 0); - glDisable(GL_LIGHTING); - - double c = 30.0/255; - glColor4d(c, c, c, 1.0); - ssglAxisAlignedQuad(aleft, aright, atop, abot); + if(canvas) { + canvas->DrawRect(aleft, aright, atop, abot, + /*fillColor=*/{ 30, 30, 30, 255 }, + /*outlineColor=*/{}); } - struct { - bool show; - const char *str; - } toolTip = { false, NULL }; - bool leftpos = true; - for(i = 0; Toolbar[i].image; i++) { - if(Toolbar[i].image == SPACER) { + for(ToolIcon &icon : Toolbar) { + if(icon.name.empty()) { // spacer if(!leftpos) { leftpos = true; y -= 32; @@ -150,45 +183,43 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, } y -= 16; - if(paint) { + if(canvas) { // Draw a separator bar in a slightly different color. int divw = 30, divh = 2; - glColor4d(0.17, 0.17, 0.17, 1); - x += 16; - y += 24; - ssglAxisAlignedQuad(x+divw, x-divw, y+divh, y-divh); - x -= 16; - y -= 24; + canvas->DrawRect(x+16+divw, x+16-divw, y+24+divh, y+24-divh, + /*fillColor=*/{ 45, 45, 45, 255 }, + /*outlineColor=*/{}); } continue; } - if(paint) { - glRasterPos2i(x - 12, y - 12); - glDrawPixels(24, 24, GL_RGB, GL_UNSIGNED_BYTE, Toolbar[i].image); + if(icon.pixmap == nullptr) { + icon.pixmap = LoadPng("icons/graphics-window/" + icon.name + ".png"); + } - if(toolbarHovered == Toolbar[i].menu || - pending.operation == Toolbar[i].menu) { - // Highlight the hovered or pending item. - glColor4d(1, 1, 0, 0.3); - int boxhw = 15; - ssglAxisAlignedQuad(x+boxhw, x-boxhw, y+boxhw, y-boxhw); - } + if(canvas) { + canvas->DrawPixmap(icon.pixmap, + x - (int)icon.pixmap->width / 2, + y - (int)icon.pixmap->height / 2); - if(toolbarTooltipped == Toolbar[i].menu) { - // Display the tool tip for this item; postpone till later - // so that no one draws over us. Don't need position since - // that's just wherever the mouse is. - toolTip.show = true; - toolTip.str = Toolbar[i].tip; + if(toolbarHovered == icon.command || + (pending.operation == Pending::COMMAND && + pending.command == icon.command)) { + // Highlight the hovered or pending item. + const int boxhw = 15; + canvas->DrawRect(x+boxhw, x-boxhw, y+boxhw, y-boxhw, + /*fillColor=*/{ 255, 255, 0, 75 }, + /*outlineColor=*/{}); } } else { - int boxhw = 16; + const int boxhw = 16; if(mx < (x+boxhw) && mx > (x - boxhw) && my < (y+boxhw) && my > (y - boxhw)) { - if(menuHit) *menuHit = Toolbar[i].menu; + if(hitCommand) *hitCommand = icon.command; + if(hitX) *hitX = x - boxhw; + if(hitY) *hitY = (int)height - (y + boxhw); } } @@ -202,47 +233,5 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, } } - if(paint) { - // Do this last so that nothing can draw over it. - if(toolTip.show) { - ssglInitializeBitmapFont(); - std::string str = toolTip.str; - - for(i = 0; SS.GW.menu[i].level >= 0; i++) { - if(toolbarTooltipped == SS.GW.menu[i].id) { - std::string accel = MakeAcceleratorLabel(SS.GW.menu[i].accel); - if(!accel.empty()) { - str += ssprintf(" (%s)", accel.c_str()); - } - break; - } - } - - int tw = str.length() * (SS.TW.CHAR_WIDTH - 1) + 10, - th = SS.TW.LINE_HEIGHT + 2; - - double ox = toolbarMouseX + 3, oy = toolbarMouseY + 3; - glLineWidth(1); - glColor4d(1.0, 1.0, 0.6, 1.0); - ssglAxisAlignedQuad(ox, ox+tw, oy, oy+th); - glColor4d(0.0, 0.0, 0.0, 1.0); - ssglAxisAlignedLineLoop(ox, ox+tw, oy, oy+th); - - glColor4d(0, 0, 0, 1); - glPushMatrix(); - glTranslated(ox+5, oy+3, 0); - glScaled(1, -1, 1); - ssglBitmapText(str, Vector::From(0, 0, 0)); - glPopMatrix(); - } - ssglDepthRangeLockToFront(false); - } - return withinToolbar; } - -void GraphicsWindow::TimerCallback(void) { - SS.GW.toolbarTooltipped = SS.GW.toolbarHovered; - PaintGraphics(); -} - diff --git a/src/ttf.cpp b/src/ttf.cpp index 79cbffd..ddb2aa1 100644 --- a/src/ttf.cpp +++ b/src/ttf.cpp @@ -56,41 +56,62 @@ TtfFontList::~TtfFontList() { void TtfFontList::LoadAll() { if(loaded) return; - for(const std::string &font : GetFontFiles()) { + for(const Platform::Path &font : Platform::GetFontFiles()) { TtfFont tf = {}; tf.fontFile = font; if(tf.LoadFromFile(fontLibrary)) l.Add(&tf); } + // Add builtin font to end of font list so it is displayed first in the UI + { + TtfFont tf = {}; + tf.SetResourceID("fonts/BitstreamVeraSans-Roman-builtin.ttf"); + if(tf.LoadFromResource(fontLibrary)) + l.Add(&tf); + } + // Sort fonts according to their actual name, not filename. - std::sort(&l.elem[0], &l.elem[l.n], + std::sort(l.begin(), l.end(), [](const TtfFont &a, const TtfFont &b) { return a.name < b.name; }); // Filter out fonts with the same family and style name. This is not // strictly necessarily the exact same font, but it will almost always be. - TtfFont *it = std::unique(&l.elem[0], &l.elem[l.n], - [](const TtfFont &a, const TtfFont &b) { return a.name == b.name; }); - l.RemoveLast(&l.elem[l.n] - it); + TtfFont *it = std::unique(l.begin(), l.end(), + [](const TtfFont &a, const TtfFont &b) { return a.name == b.name; }); + l.RemoveLast(&l[l.n] - it); - // TODO: identify fonts by their name and not filename, which may change - // between OSes. + //! @todo identify fonts by their name and not filename, which may change + //! between OSes. loaded = true; } -void TtfFontList::PlotString(const std::string &font, const std::string &str, - SBezierList *sbl, Vector origin, Vector u, Vector v) +TtfFont *TtfFontList::LoadFont(const std::string &font) { LoadAll(); - TtfFont *tf = std::find_if(&l.elem[0], &l.elem[l.n], - [&](const TtfFont &tf) { return tf.FontFileBaseName() == font; }); + TtfFont *tf = std::find_if(l.begin(), l.end(), + [&font](const TtfFont &tf) { return tf.FontFileBaseName() == font; }); - if(!str.empty() && tf != &l.elem[l.n]) { + if(tf != l.end()) { if(tf->fontFace == NULL) { - tf->LoadFromFile(fontLibrary, /*nameOnly=*/false); + if(tf->IsResource()) + tf->LoadFromResource(fontLibrary, /*keepOpen=*/true); + else + tf->LoadFromFile(fontLibrary, /*keepOpen=*/true); } + return tf; + } else { + return NULL; + } +} + +void TtfFontList::PlotString(const std::string &font, const std::string &str, + SBezierList *sbl, Vector origin, Vector u, Vector v) +{ + TtfFont *tf = LoadFont(font); + if(!str.empty() && tf != NULL) { tf->PlotString(str, sbl, origin, u, v); } else { // No text or no font; so draw a big X for an error marker. @@ -102,40 +123,89 @@ void TtfFontList::PlotString(const std::string &font, const std::string &str, } } +double TtfFontList::AspectRatio(const std::string &font, const std::string &str) +{ + TtfFont *tf = LoadFont(font); + if(tf != NULL) { + return tf->AspectRatio(str); + } + + return 0.0; +} + //----------------------------------------------------------------------------- // Return the basename of our font filename; that's how the requests and // entities that reference us will store it. //----------------------------------------------------------------------------- std::string TtfFont::FontFileBaseName() const { - std::string baseName = fontFile; - size_t pos = baseName.rfind(PATH_SEP); - if(pos != std::string::npos) - return baseName.erase(0, pos + 1); - return ""; + return fontFile.FileName(); } //----------------------------------------------------------------------------- -// Load a TrueType font into memory. We care about the curves that define -// the letter shapes, and about the mappings that determine which glyph goes -// with which character. +// Convenience method to set fontFile for resource-loaded fonts as res:// +//----------------------------------------------------------------------------- +void TtfFont::SetResourceID(const std::string &resource) { + fontFile = { "res://" + resource }; +} + +bool TtfFont::IsResource() const { + return fontFile.raw.compare(0, 6, "res://") == 0; +} + //----------------------------------------------------------------------------- -bool TtfFont::LoadFromFile(FT_Library fontLibrary, bool nameOnly) { +// Load a TrueType font into memory. +//----------------------------------------------------------------------------- +bool TtfFont::LoadFromFile(FT_Library fontLibrary, bool keepOpen) { + ssassert(!IsResource(), "Cannot load a font provided by a resource as a file."); + FT_Open_Args args = {}; args.flags = FT_OPEN_PATHNAME; - args.pathname = &fontFile[0]; // FT_String is char* for historical reasons + args.pathname = &fontFile.raw[0]; // FT_String is char* for historical reasons - // We don't use ssfopen() here to let freetype do its own memory management. + // We don't use OpenFile() here to let freetype do its own memory management. // This is OK because on Linux/OS X we just delegate to fopen and on Windows // we only look into C:\Windows\Fonts, which has a known short path. if(int fterr = FT_Open_Face(fontLibrary, &args, 0, &fontFace)) { dbp("freetype: loading font from file '%s' failed: %s", - fontFile.c_str(), ft_error_string(fterr)); + fontFile.raw.c_str(), ft_error_string(fterr)); return false; } + return ExtractTTFData(keepOpen); +} + +//----------------------------------------------------------------------------- +// Load a TrueType from resource in memory. Implemented to load bundled fonts +// through theresource system. +//----------------------------------------------------------------------------- +bool TtfFont::LoadFromResource(FT_Library fontLibrary, bool keepOpen) { + ssassert(IsResource(), "Font to be loaded as resource is not provided by a resource " + "or does not have the 'res://' prefix."); + + size_t _size; + // substr to cut off 'res://' (length: 6) + const void *_buffer = Platform::LoadResource(fontFile.raw.substr(6, fontFile.raw.size()), + &_size); + + FT_Long size = static_cast(_size); + const FT_Byte *buffer = reinterpret_cast(_buffer); + + if(int fterr = FT_New_Memory_Face(fontLibrary, buffer, size, 0, &fontFace)) { + dbp("freetype: loading font '%s' from memory failed: %s", + fontFile.raw.c_str(), ft_error_string(fterr)); + return false; + } + + return ExtractTTFData(keepOpen); +} + +//----------------------------------------------------------------------------- +// Extract font information. We care about the font name and unit size. +//----------------------------------------------------------------------------- +bool TtfFont::ExtractTTFData(bool keepOpen) { if(int fterr = FT_Select_Charmap(fontFace, FT_ENCODING_UNICODE)) { dbp("freetype: loading unicode CMap for file '%s' failed: %s", - fontFile.c_str(), ft_error_string(fterr)); + fontFile.raw.c_str(), ft_error_string(fterr)); FT_Done_Face(fontFace); fontFace = NULL; return false; @@ -144,12 +214,6 @@ bool TtfFont::LoadFromFile(FT_Library fontLibrary, bool nameOnly) { name = std::string(fontFace->family_name) + " (" + std::string(fontFace->style_name) + ")"; - if(nameOnly) { - FT_Done_Face(fontFace); - fontFace = NULL; - return true; - } - // We always ask Freetype to give us a unit size character. // It uses fixed point; put the unit size somewhere in the middle of the dynamic // range of its 26.6 fixed point type, and adjust the factors so that the unit @@ -161,8 +225,8 @@ bool TtfFont::LoadFromFile(FT_Library fontLibrary, bool nameOnly) { sizeRequest.horiResolution = 128; sizeRequest.vertResolution = 128; if(int fterr = FT_Request_Size(fontFace, &sizeRequest)) { - dbp("freetype: cannot set character size: %s", - ft_error_string(fterr)); + dbp("freetype: size request for file '%s' failed: %s", + fontFile.raw.c_str(), ft_error_string(fterr)); FT_Done_Face(fontFace); fontFace = NULL; return false; @@ -171,15 +235,17 @@ bool TtfFont::LoadFromFile(FT_Library fontLibrary, bool nameOnly) { char chr = 'A'; uint32_t gid = FT_Get_Char_Index(fontFace, 'A'); if (gid == 0) { - dbp("freetype: CID-to-GID mapping for CID 0x%04x failed: %s; using CID as GID", - chr, ft_error_string(gid)); - gid = chr; + dbp("freetype: CID-to-GID mapping for CID 0x%04x in file '%s' failed: %s; " + "using CID as GID", + chr, fontFile.raw.c_str(), ft_error_string(gid)); + dbp("Assuming cap height is the same as requested height (this is likely wrong)."); + capHeight = (double)sizeRequest.height; } if(gid) { if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) { - dbp("freetype: cannot load glyph for GID 0x%04x: %s", - gid, ft_error_string(fterr)); + dbp("freetype: cannot load glyph for GID 0x%04x in file '%s': %s", + gid, fontFile.raw.c_str(), ft_error_string(fterr)); FT_Done_Face(fontFace); fontFace = NULL; return false; @@ -190,6 +256,15 @@ bool TtfFont::LoadFromFile(FT_Library fontLibrary, bool nameOnly) { capHeight = (double)bbox.yMax; } + // If we just wanted to get the font's name and figure out if it's actually usable, close + // it now. If we don't do this, and there are a lot of fonts, we can bump into the file + // descriptor limit (especially on Windows), breaking all file operations. + if(!keepOpen) { + FT_Done_Face(fontFace); + fontFace = NULL; + return true; + } + return true; } @@ -258,7 +333,7 @@ static int CubicTo(const FT_Vector *c1, const FT_Vector *c2, const FT_Vector *p, void TtfFont::PlotString(const std::string &str, SBezierList *sbl, Vector origin, Vector u, Vector v) { - if(fontFace == NULL) oops(); + ssassert(fontFace != NULL, "Expected font face to be loaded"); FT_Outline_Funcs outlineFuncs; outlineFuncs.move_to = MoveTo; @@ -272,8 +347,9 @@ void TtfFont::PlotString(const std::string &str, for(char32_t cid : ReadUTF8(str)) { uint32_t gid = FT_Get_Char_Index(fontFace, cid); if (gid == 0) { - dbp("freetype: CID-to-GID mapping for CID 0x%04x failed: %s; using CID as GID", - cid, ft_error_string(gid)); + dbp("freetype: CID-to-GID mapping for CID 0x%04x in file '%s' failed: %s; " + "using CID as GID", + cid, fontFile.raw.c_str(), ft_error_string(gid)); gid = cid; } @@ -286,8 +362,8 @@ void TtfFont::PlotString(const std::string &str, * ones, antialiasing mitigates this considerably though. */ if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) { - dbp("freetype: cannot load glyph for GID 0x%04x: %s", - gid, ft_error_string(fterr)); + dbp("freetype: cannot load glyph for GID 0x%04x in file '%s': %s", + gid, fontFile.raw.c_str(), ft_error_string(fterr)); return; } @@ -316,11 +392,11 @@ void TtfFont::PlotString(const std::string &str, data.u = u; data.v = v; data.beziers = sbl; - data.factor = 1.0 / capHeight; + data.factor = (float)(1.0 / capHeight); data.bx = bx; if(int fterr = FT_Outline_Decompose(&fontFace->glyph->outline, &outlineFuncs, &data)) { - dbp("freetype: bezier decomposition failed (gid %d): %s", - gid, ft_error_string(fterr)); + dbp("freetype: bezier decomposition failed for GID 0x%4x in file '%s': %s", + gid, fontFile.raw.c_str(), ft_error_string(fterr)); } // And we're done, so advance our position by the requested advance @@ -328,3 +404,28 @@ void TtfFont::PlotString(const std::string &str, dx += fontFace->glyph->advance.x; } } + +double TtfFont::AspectRatio(const std::string &str) { + ssassert(fontFace != NULL, "Expected font face to be loaded"); + + // We always request a unit size character, so the aspect ratio is the same as advance length. + double dx = 0; + for(char32_t chr : ReadUTF8(str)) { + uint32_t gid = FT_Get_Char_Index(fontFace, chr); + if (gid == 0) { + dbp("freetype: CID-to-GID mapping for CID 0x%04x in file '%s' failed: %s; " + "using CID as GID", + chr, fontFile.raw.c_str(), ft_error_string(gid)); + } + + if(int fterr = FT_Load_Glyph(fontFace, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) { + dbp("freetype: cannot load glyph for GID 0x%04x in file '%s': %s", + gid, fontFile.raw.c_str(), ft_error_string(fterr)); + break; + } + + dx += (double)fontFace->glyph->advance.x / capHeight; + } + + return dx; +} diff --git a/src/ttf.h b/src/ttf.h index ec91127..565e6a9 100644 --- a/src/ttf.h +++ b/src/ttf.h @@ -6,21 +6,28 @@ // Copyright 2016 whitequark, Peter Barfuss. //----------------------------------------------------------------------------- -#ifndef __TTF_H -#define __TTF_H +#ifndef SOLVESPACE_TTF_H +#define SOLVESPACE_TTF_H class TtfFont { public: - std::string fontFile; + Platform::Path fontFile; // or resource path/name as res:// std::string name; FT_FaceRec_ *fontFace; double capHeight; + void SetResourceID(const std::string &resource); + bool IsResource() const; + std::string FontFileBaseName() const; - bool LoadFromFile(FT_LibraryRec_ *fontLibrary, bool nameOnly = true); + bool LoadFromFile(FT_LibraryRec_ *fontLibrary, bool keepOpen = false); + bool LoadFromResource(FT_LibraryRec_ *fontLibrary, bool keepOpen = false); void PlotString(const std::string &str, SBezierList *sbl, Vector origin, Vector u, Vector v); + double AspectRatio(const std::string &str); + + bool ExtractTTFData(bool keepOpen); }; class TtfFontList { @@ -33,9 +40,11 @@ public: ~TtfFontList(); void LoadAll(); + TtfFont *LoadFont(const std::string &font); void PlotString(const std::string &font, const std::string &str, SBezierList *sbl, Vector origin, Vector u, Vector v); + double AspectRatio(const std::string &font, const std::string &str); }; #endif diff --git a/src/ui.h b/src/ui.h index a041ed3..8bdd701 100644 --- a/src/ui.h +++ b/src/ui.h @@ -5,15 +5,181 @@ // Copyright 2008-2013 Jonathan Westhues. //----------------------------------------------------------------------------- -#ifndef __UI_H -#define __UI_H +#ifndef SOLVESPACE_UI_H +#define SOLVESPACE_UI_H + +class Locale { +public: + std::string language; + std::string region; + uint16_t lcid; + std::string displayName; + + std::string Culture() const { + return language + "-" + region; + } +}; + +struct LocaleLess { + bool operator()(const Locale &a, const Locale &b) const { + return a.language < b.language || + (a.language == b.language && a.region < b.region); + } +}; + +const std::set &Locales(); +bool SetLocale(const std::string &name); +bool SetLocale(uint16_t lcid); + +const std::string &Translate(const char *msgid); +const std::string &Translate(const char *msgctxt, const char *msgid); +const std::string &TranslatePlural(const char *msgid, unsigned n); +const std::string &TranslatePlural(const char *msgctxt, const char *msgid, unsigned n); + +inline const char *N_(const char *msgid) { + return msgid; +} +inline const char *CN_(const char *msgctxt, const char *msgid) { + return msgid; +} +#if defined(LIBRARY) +inline const char *_(const char *msgid) { + return msgid; +} +inline const char *C_(const char *msgctxt, const char *msgid) { + return msgid; +} +#else +inline const char *_(const char *msgid) { + return Translate(msgid).c_str(); +} +inline const char *C_(const char *msgctxt, const char *msgid) { + return Translate(msgctxt, msgid).c_str(); +} +#endif + +// This table describes the top-level menus in the graphics window. +enum class Command : uint32_t { + NONE = 0, + // File + NEW = 100, + OPEN, + OPEN_RECENT, + SAVE, + SAVE_AS, + EXPORT_IMAGE, + EXPORT_MESH, + EXPORT_SURFACES, + EXPORT_VIEW, + EXPORT_SECTION, + EXPORT_WIREFRAME, + IMPORT, + EXIT, + // View + ZOOM_IN, + ZOOM_OUT, + ZOOM_TO_FIT, + SHOW_GRID, + PERSPECTIVE_PROJ, + ONTO_WORKPLANE, + NEAREST_ORTHO, + NEAREST_ISO, + CENTER_VIEW, + SHOW_TOOLBAR, + SHOW_TEXT_WND, + UNITS_INCHES, + UNITS_MM, + UNITS_METERS, + FULL_SCREEN, + // Edit + UNDO, + REDO, + CUT, + COPY, + PASTE, + PASTE_TRANSFORM, + DELETE, + SELECT_CHAIN, + SELECT_ALL, + SNAP_TO_GRID, + ROTATE_90, + UNSELECT_ALL, + REGEN_ALL, + EDIT_LINE_STYLES, + VIEW_PROJECTION, + CONFIGURATION, + // Request + SEL_WORKPLANE, + FREE_IN_3D, + DATUM_POINT, + WORKPLANE, + LINE_SEGMENT, + CONSTR_SEGMENT, + CIRCLE, + ARC, + RECTANGLE, + CUBIC, + TTF_TEXT, + IMAGE, + SPLIT_CURVES, + TANGENT_ARC, + CONSTRUCTION, + // Group + GROUP_3D, + GROUP_WRKPL, + GROUP_EXTRUDE, + GROUP_HELIX, + GROUP_LATHE, + GROUP_REVOLVE, + GROUP_ROT, + GROUP_TRANS, + GROUP_LINK, + GROUP_RECENT, + // Constrain + DISTANCE_DIA, + REF_DISTANCE, + ANGLE, + REF_ANGLE, + OTHER_ANGLE, + REFERENCE, + EQUAL, + RATIO, + DIFFERENCE, + ON_ENTITY, + SYMMETRIC, + AT_MIDPOINT, + HORIZONTAL, + VERTICAL, + PARALLEL, + PERPENDICULAR, + ORIENTED_SAME, + WHERE_DRAGGED, + COMMENT, + // Analyze + VOLUME, + AREA, + PERIMETER, + INTERFERENCE, + NAKED_EDGES, + SHOW_DOF, + CENTER_OF_MASS, + TRACE_PT, + STOP_TRACING, + STEP_DIM, + // Help + LOCALE, + WEBSITE, + ABOUT, +}; + +class Button; class TextWindow { public: enum { MAX_COLS = 100, MIN_COLS = 45, - MAX_ROWS = 2000 + MAX_ROWS = 4000 }; typedef struct { @@ -27,7 +193,7 @@ public: float fgColorTable[256*3]; enum { - CHAR_WIDTH = 9, + CHAR_WIDTH_ = 9, CHAR_HEIGHT = 16, LINE_HEIGHT = 20, LEFT_MARGIN = 6, @@ -55,72 +221,63 @@ public: } meta[MAX_ROWS][MAX_COLS]; int hoveredRow, hoveredCol; - int top[MAX_ROWS]; // in half-line units, or -1 for unused int rows; - // The row of icons at the top of the text window, to hide/show things - typedef struct { - bool *var; - uint8_t *icon; - const char *tip; - } HideShowIcon; - static HideShowIcon hideShowIcons[]; - static bool SPACER; - - // These are called by the platform-specific code. - void Paint(void); + Platform::WindowRef window; + std::shared_ptr canvas; + + void Draw(Canvas *canvas); + + void Paint(); void MouseEvent(bool isClick, bool leftDown, double x, double y); - void MouseScroll(double x, double y, int delta); - void MouseLeave(void); - void ScrollbarEvent(int newPos); + void MouseLeave(); + void ScrollbarEvent(double newPos); - enum { + enum DrawOrHitHow : uint32_t { PAINT = 0, HOVER = 1, CLICK = 2 }; - void DrawOrHitTestIcons(int how, double mx, double my); - void TimerCallback(void); - Point2d oldMousePos; - HideShowIcon *hoveredIcon, *tooltippedIcon; + void DrawOrHitTestIcons(UiCanvas *canvas, DrawOrHitHow how, + double mx, double my); + Button *hoveredButton; Vector HsvToRgb(Vector hsv); - uint8_t *HsvPattern2d(void); - uint8_t *HsvPattern1d(double h, double s); - void ColorPickerDone(void); - bool DrawOrHitTestColorPicker(int how, bool leftDown, double x, double y); + std::shared_ptr HsvPattern2d(int w, int h); + std::shared_ptr HsvPattern1d(double hue, double sat, int w, int h); + void ColorPickerDone(); + bool DrawOrHitTestColorPicker(UiCanvas *canvas, DrawOrHitHow how, + bool leftDown, double x, double y); - void Init(void); + void Init(); void MakeColorTable(const Color *in, float *out); void Printf(bool half, const char *fmt, ...); - void ClearScreen(void); + void ClearScreen(); - void Show(void); + void Show(); + void Resize(); // State for the screen that we are showing in the text window. - enum { - SCREEN_LIST_OF_GROUPS = 0, - SCREEN_GROUP_INFO = 1, - SCREEN_GROUP_SOLVE_INFO = 2, - SCREEN_CONFIGURATION = 3, - SCREEN_STEP_DIMENSION = 4, - SCREEN_LIST_OF_STYLES = 5, - SCREEN_STYLE_INFO = 6, - SCREEN_PASTE_TRANSFORMED = 7, - SCREEN_EDIT_VIEW = 8, - SCREEN_TANGENT_ARC = 9 + enum class Screen : uint32_t { + LIST_OF_GROUPS = 0, + GROUP_INFO = 1, + GROUP_SOLVE_INFO = 2, + CONFIGURATION = 3, + STEP_DIMENSION = 4, + LIST_OF_STYLES = 5, + STYLE_INFO = 6, + PASTE_TRANSFORMED = 7, + EDIT_VIEW = 8, + TANGENT_ARC = 9 }; typedef struct { - int screen; + Screen screen; hGroup group; hStyle style; hConstraint constraint; - bool dimIsDistance; - double dimFinish; - int dimSteps; struct { int times; @@ -132,61 +289,63 @@ public: } ShownState; ShownState shown; - enum { - EDIT_NOTHING = 0, + enum class Edit : uint32_t { + NOTHING = 0, // For multiple groups - EDIT_TIMES_REPEATED = 1, - EDIT_GROUP_NAME = 2, - EDIT_GROUP_SCALE = 3, - EDIT_GROUP_COLOR = 4, - EDIT_GROUP_OPACITY = 5, - // For the configuraiton screen - EDIT_LIGHT_DIRECTION = 100, - EDIT_LIGHT_INTENSITY = 101, - EDIT_COLOR = 102, - EDIT_CHORD_TOLERANCE = 103, - EDIT_MAX_SEGMENTS = 104, - EDIT_CAMERA_TANGENT = 105, - EDIT_GRID_SPACING = 106, - EDIT_DIGITS_AFTER_DECIMAL = 107, - EDIT_EXPORT_SCALE = 108, - EDIT_EXPORT_OFFSET = 109, - EDIT_CANVAS_SIZE = 110, - EDIT_G_CODE_DEPTH = 120, - EDIT_G_CODE_PASSES = 121, - EDIT_G_CODE_FEED = 122, - EDIT_G_CODE_PLUNGE_FEED = 123, - EDIT_AUTOSAVE_INTERVAL = 124, + TIMES_REPEATED = 1, + GROUP_NAME = 2, + GROUP_SCALE = 3, + GROUP_COLOR = 4, + GROUP_OPACITY = 5, + // For the configuration screen + LIGHT_DIRECTION = 100, + LIGHT_INTENSITY = 101, + COLOR = 102, + CHORD_TOLERANCE = 103, + MAX_SEGMENTS = 104, + CAMERA_TANGENT = 105, + GRID_SPACING = 106, + DIGITS_AFTER_DECIMAL = 107, + DIGITS_AFTER_DECIMAL_DEGREE = 108, + EXPORT_SCALE = 109, + EXPORT_OFFSET = 110, + CANVAS_SIZE = 111, + G_CODE_DEPTH = 112, + G_CODE_PASSES = 113, + G_CODE_FEED = 114, + G_CODE_PLUNGE_FEED = 115, + AUTOSAVE_INTERVAL = 116, + LIGHT_AMBIENT = 117, + FIND_CONSTRAINT_TIMEOUT = 118, // For TTF text - EDIT_TTF_TEXT = 300, + TTF_TEXT = 300, // For the step dimension screen - EDIT_STEP_DIM_FINISH = 400, - EDIT_STEP_DIM_STEPS = 401, + STEP_DIM_FINISH = 400, + STEP_DIM_STEPS = 401, // For the styles stuff - EDIT_STYLE_WIDTH = 500, - EDIT_STYLE_TEXT_HEIGHT = 501, - EDIT_STYLE_TEXT_ANGLE = 502, - EDIT_STYLE_COLOR = 503, - EDIT_STYLE_FILL_COLOR = 504, - EDIT_STYLE_NAME = 505, - EDIT_BACKGROUND_COLOR = 506, - EDIT_BACKGROUND_IMG_SCALE = 507, - EDIT_STYLE_STIPPLE_PERIOD = 508, + STYLE_WIDTH = 500, + STYLE_TEXT_HEIGHT = 501, + STYLE_TEXT_ANGLE = 502, + STYLE_COLOR = 503, + STYLE_FILL_COLOR = 504, + STYLE_NAME = 505, + BACKGROUND_COLOR = 506, + STYLE_STIPPLE_PERIOD = 508, // For paste transforming - EDIT_PASTE_TIMES_REPEATED = 600, - EDIT_PASTE_ANGLE = 601, - EDIT_PASTE_SCALE = 602, + PASTE_TIMES_REPEATED = 600, + PASTE_ANGLE = 601, + PASTE_SCALE = 602, // For view - EDIT_VIEW_SCALE = 700, - EDIT_VIEW_ORIGIN = 701, - EDIT_VIEW_PROJ_RIGHT = 702, - EDIT_VIEW_PROJ_UP = 703, + VIEW_SCALE = 700, + VIEW_ORIGIN = 701, + VIEW_PROJ_RIGHT = 702, + VIEW_PROJ_UP = 703, // For tangent arc - EDIT_TANGENT_ARC_RADIUS = 800 + TANGENT_ARC_RADIUS = 800 }; struct { bool showAgain; - int meaning; + Edit meaning; int i; hGroup group; hRequest request; @@ -208,29 +367,29 @@ public: } colorPicker; } editControl; - void HideEditControl(void); + void HideEditControl(); void ShowEditControl(int col, const std::string &str, int halfRow = -1); void ShowEditControlWithColorPicker(int col, RgbaColor rgb); - void ClearSuper(void); + void ClearSuper(); void ShowHeader(bool withNav); // These are self-contained screens, that show some information about // the sketch. - void ShowListOfGroups(void); - void ShowGroupInfo(void); - void ShowGroupSolveInfo(void); - void ShowConfiguration(void); - void ShowListOfStyles(void); - void ShowStyleInfo(void); - void ShowStepDimension(void); - void ShowPasteTransformed(void); - void ShowEditView(void); - void ShowTangentArc(void); + void ShowListOfGroups(); + void ShowGroupInfo(); + void ShowGroupSolveInfo(); + void ShowConfiguration(); + void ShowListOfStyles(); + void ShowStyleInfo(); + void ShowStepDimension(); + void ShowPasteTransformed(); + void ShowEditView(); + void ShowTangentArc(); // Special screen, based on selection - void DescribeSelection(void); + void DescribeSelection(); - void GoToScreen(int screen); + void GoToScreen(Screen screen); // All of these are callbacks from the GUI code; first from when // we're describing an entity @@ -239,6 +398,7 @@ public: static void ScreenUnselectAll(int link, uint32_t v); // when we're describing a constraint + static void ScreenConstraintToggleReference(int link, uint32_t v); static void ScreenConstraintShowAsRadius(int link, uint32_t v); // and the rest from the stuff in textscreens.cpp @@ -249,9 +409,12 @@ public: static void ScreenShowGroupsSpecial(int link, uint32_t v); static void ScreenDeleteGroup(int link, uint32_t v); - static void ScreenHoverConstraint(int link, uint32_t v); + static void ScreenHoverGroupWorkplane(int link, uint32_t v); static void ScreenHoverRequest(int link, uint32_t v); + static void ScreenHoverEntity(int link, uint32_t v); + static void ScreenHoverConstraint(int link, uint32_t v); static void ScreenSelectRequest(int link, uint32_t v); + static void ScreenSelectEntity(int link, uint32_t v); static void ScreenSelectConstraint(int link, uint32_t v); static void ScreenChangeGroupOption(int link, uint32_t v); @@ -266,15 +429,19 @@ public: static void ScreenCreateCustomStyle(int link, uint32_t v); static void ScreenLoadFactoryDefaultStyles(int link, uint32_t v); static void ScreenAssignSelectionToStyle(int link, uint32_t v); - static void ScreenBackgroundImage(int link, uint32_t v); static void ScreenShowConfiguration(int link, uint32_t v); static void ScreenShowEditView(int link, uint32_t v); static void ScreenGoToWebsite(int link, uint32_t v); static void ScreenChangeFixExportColors(int link, uint32_t v); + static void ScreenChangeExportBackgroundColor(int link, uint32_t v); static void ScreenChangeBackFaces(int link, uint32_t v); + static void ScreenChangeShowContourAreas(int link, uint32_t v); static void ScreenChangeCheckClosedContour(int link, uint32_t v); + static void ScreenChangeTurntableNav(int link, uint32_t v); + static void ScreenChangeImmediatelyEditDimension(int link, uint32_t v); + static void ScreenChangeAutomaticLineConstraints(int link, uint32_t v); static void ScreenChangePwlCurves(int link, uint32_t v); static void ScreenChangeCanvasSizeAuto(int link, uint32_t v); static void ScreenChangeCanvasSize(int link, uint32_t v); @@ -282,6 +449,15 @@ public: static void ScreenAllowRedundant(int link, uint32_t v); + struct { + bool isDistance; + double finish; + int steps; + + Platform::TimerRef timer; + int64_t time; + int step; + } stepDim; static void ScreenStepDimSteps(int link, uint32_t v); static void ScreenStepDimFinish(int link, uint32_t v); static void ScreenStepDimGo(int link, uint32_t v); @@ -298,6 +474,7 @@ public: static void ScreenChangeGroupScale(int link, uint32_t v); static void ScreenChangeLightDirection(int link, uint32_t v); static void ScreenChangeLightIntensity(int link, uint32_t v); + static void ScreenChangeLightAmbient(int link, uint32_t v); static void ScreenChangeColor(int link, uint32_t v); static void ScreenChangeChordTolerance(int link, uint32_t v); static void ScreenChangeMaxSegments(int link, uint32_t v); @@ -306,171 +483,74 @@ public: static void ScreenChangeCameraTangent(int link, uint32_t v); static void ScreenChangeGridSpacing(int link, uint32_t v); static void ScreenChangeDigitsAfterDecimal(int link, uint32_t v); + static void ScreenChangeDigitsAfterDecimalDegree(int link, uint32_t v); + static void ScreenChangeUseSIPrefixes(int link, uint32_t v); static void ScreenChangeExportScale(int link, uint32_t v); static void ScreenChangeExportOffset(int link, uint32_t v); static void ScreenChangeGCodeParameter(int link, uint32_t v); static void ScreenChangeAutosaveInterval(int link, uint32_t v); + static void ScreenChangeFindConstraintTimeout(int link, uint32_t v); static void ScreenChangeStyleName(int link, uint32_t v); static void ScreenChangeStyleMetric(int link, uint32_t v); static void ScreenChangeStyleTextAngle(int link, uint32_t v); static void ScreenChangeStyleColor(int link, uint32_t v); static void ScreenChangeBackgroundColor(int link, uint32_t v); - static void ScreenChangeBackgroundImageScale(int link, uint32_t v); static void ScreenChangePasteTransformed(int link, uint32_t v); static void ScreenChangeViewScale(int link, uint32_t v); + static void ScreenChangeViewToFullScale(int link, uint32_t v); static void ScreenChangeViewOrigin(int link, uint32_t v); static void ScreenChangeViewProjection(int link, uint32_t v); - bool EditControlDoneForStyles(const char *s); - bool EditControlDoneForConfiguration(const char *s); - bool EditControlDoneForPaste(const char *s); - bool EditControlDoneForView(const char *s); - void EditControlDone(const char *s); + bool EditControlDoneForStyles(const std::string &s); + bool EditControlDoneForConfiguration(const std::string &s); + bool EditControlDoneForPaste(const std::string &s); + bool EditControlDoneForView(const std::string &s); + void EditControlDone(std::string s); }; -#define SELECTION_RADIUS 10.0 - class GraphicsWindow { public: - void Init(void); - - // This table describes the top-level menus in the graphics winodw. - typedef enum { - // File - MNU_NEW = 100, - MNU_OPEN, - MNU_OPEN_RECENT, - MNU_SAVE, - MNU_SAVE_AS, - MNU_EXPORT_PNG, - MNU_EXPORT_MESH, - MNU_EXPORT_SURFACES, - MNU_EXPORT_VIEW, - MNU_EXPORT_SECTION, - MNU_EXPORT_WIREFRAME, - MNU_IMPORT, - MNU_EXIT, - // View - MNU_ZOOM_IN, - MNU_ZOOM_OUT, - MNU_ZOOM_TO_FIT, - MNU_SHOW_GRID, - MNU_PERSPECTIVE_PROJ, - MNU_ONTO_WORKPLANE, - MNU_NEAREST_ORTHO, - MNU_NEAREST_ISO, - MNU_CENTER_VIEW, - MNU_SHOW_MENU_BAR, - MNU_SHOW_TOOLBAR, - MNU_SHOW_TEXT_WND, - MNU_UNITS_INCHES, - MNU_UNITS_MM, - MNU_FULL_SCREEN, - // Edit - MNU_UNDO, - MNU_REDO, - MNU_CUT, - MNU_COPY, - MNU_PASTE, - MNU_PASTE_TRANSFORM, - MNU_DELETE, - MNU_SELECT_CHAIN, - MNU_SELECT_ALL, - MNU_SNAP_TO_GRID, - MNU_ROTATE_90, - MNU_UNSELECT_ALL, - MNU_REGEN_ALL, - // Request - MNU_SEL_WORKPLANE, - MNU_FREE_IN_3D, - MNU_DATUM_POINT, - MNU_WORKPLANE, - MNU_LINE_SEGMENT, - MNU_CONSTR_SEGMENT, - MNU_CIRCLE, - MNU_ARC, - MNU_RECTANGLE, - MNU_CUBIC, - MNU_TTF_TEXT, - MNU_SPLIT_CURVES, - MNU_TANGENT_ARC, - MNU_CONSTRUCTION, - // Group - MNU_GROUP_3D, - MNU_GROUP_WRKPL, - MNU_GROUP_EXTRUDE, - MNU_GROUP_LATHE, - MNU_GROUP_ROT, - MNU_GROUP_TRANS, - MNU_GROUP_LINK, - MNU_GROUP_RECENT, - // Constrain - MNU_DISTANCE_DIA, - MNU_REF_DISTANCE, - MNU_ANGLE, - MNU_REF_ANGLE, - MNU_OTHER_ANGLE, - MNU_REFERENCE, - MNU_EQUAL, - MNU_RATIO, - MNU_DIFFERENCE, - MNU_ON_ENTITY, - MNU_SYMMETRIC, - MNU_AT_MIDPOINT, - MNU_HORIZONTAL, - MNU_VERTICAL, - MNU_PARALLEL, - MNU_PERPENDICULAR, - MNU_ORIENTED_SAME, - MNU_WHERE_DRAGGED, - MNU_COMMENT, - // Analyze - MNU_VOLUME, - MNU_AREA, - MNU_INTERFERENCE, - MNU_NAKED_EDGES, - MNU_SHOW_DOF, - MNU_TRACE_PT, - MNU_STOP_TRACING, - MNU_STEP_DIM, - // Help, - MNU_WEBSITE, - MNU_ABOUT - } MenuId; - typedef void MenuHandler(int id); - enum { - ESCAPE_KEY = 27, - DELETE_KEY = 127, - FUNCTION_KEY_BASE = 0xf0 - }; - enum { - SHIFT_MASK = 0x100, - CTRL_MASK = 0x200 - }; - enum MenuItemKind { - MENU_ITEM_NORMAL = 0, - MENU_ITEM_CHECK, - MENU_ITEM_RADIO - }; - typedef struct { - int level; // 0 == on menu bar, 1 == one level down - const char *label; // or NULL for a separator - int id; // unique ID - int accel; // keyboard accelerator - MenuItemKind kind; - MenuHandler *fn; - } MenuEntry; - static const MenuEntry menu[]; - static void MenuView(int id); - static void MenuEdit(int id); - static void MenuRequest(int id); - void DeleteSelection(void); - void CopySelection(void); + void Init(); + + Platform::WindowRef window; + + void PopulateMainMenu(); + void PopulateRecentFiles(); + + Platform::KeyboardEvent AcceleratorForCommand(Command id); + void ActivateCommand(Command id); + + static void MenuView(Command id); + static void MenuEdit(Command id); + static void MenuRequest(Command id); + void DeleteSelection(); + void CopySelection(); void PasteClipboard(Vector trans, double theta, double scale); - static void MenuClipboard(int id); + static void MenuClipboard(Command id); + + Platform::MenuRef openRecentMenu; + Platform::MenuRef linkRecentMenu; + + Platform::MenuItemRef showGridMenuItem; + Platform::MenuItemRef perspectiveProjMenuItem; + Platform::MenuItemRef showToolbarMenuItem; + Platform::MenuItemRef showTextWndMenuItem; + Platform::MenuItemRef fullScreenMenuItem; + + Platform::MenuItemRef unitsMmMenuItem; + Platform::MenuItemRef unitsMetersMenuItem; + Platform::MenuItemRef unitsInchesMenuItem; + + Platform::MenuItemRef inWorkplaneMenuItem; + Platform::MenuItemRef in3dMenuItem; + + Platform::MenuItemRef undoMenuItem; + Platform::MenuItemRef redoMenuItem; + + std::shared_ptr canvas; + std::shared_ptr persistentCanvas; + bool persistentDirty; - // The width and height (in pixels) of the window. - double width, height; // These parameters define the map from 2d screen coordinates to the // coordinates of the 3d sketch points. We will use an axonometric // projection. @@ -504,84 +584,98 @@ public: // allowing a paint in between. The extra solves are wasted if they're // not displayed. bool havePainted; - // Similarly, don't draw edges and outlines, since that's too slow - // for real-time dragging. - bool isDegraded; // Some state for the context menu. struct { bool active; } context; - void NormalizeProjectionVectors(void); + Camera GetCamera() const; + Lighting GetLighting() const; + + void NormalizeProjectionVectors(); Point2d ProjectPoint(Vector p); Vector ProjectPoint3(Vector p); Vector ProjectPoint4(Vector p, double *w); Vector UnProjectPoint(Point2d p); Vector UnProjectPoint3(Vector p); + + Platform::TimerRef animateTimer; void AnimateOnto(Quaternion quatf, Vector offsetf); - void AnimateOntoWorkplane(void); + void AnimateOntoWorkplane(); + Vector VectorFromProjs(Vector rightUpForward); void HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin, - double *wmin, bool usePerspective); - void LoopOverPoints(const std::vector &entity, const std::vector &faces, Point2d *pmax, Point2d *pmin, - double *wmin, bool usePerspective, bool includeMesh); - void ZoomToFit(bool includingInvisibles, bool useSelection = false); + double *wmin, bool usePerspective, + const Camera &camera); + void LoopOverPoints(const std::vector &entities, + const std::vector &constraints, + const std::vector &faces, + Point2d *pmax, Point2d *pmin, + double *wmin, bool usePerspective, bool includeMesh, + const Camera &camera); + void ZoomToFit(bool includingInvisibles = false, bool useSelection = false); + double ZoomToFit(const Camera &camera, + bool includingInvisibles = false, bool useSelection = false); hGroup activeGroup; - void EnsureValidActives(void); - bool LockedInWorkplane(void); - void SetWorkplaneFreeIn3d(void); - hEntity ActiveWorkplane(void); - void ForceTextWindowShown(void); + void EnsureValidActives(); + bool LockedInWorkplane(); + void SetWorkplaneFreeIn3d(); + hEntity ActiveWorkplane(); + void ForceTextWindowShown(); // Operations that must be completed by doing something with the mouse - // are noted here. These occupy the same space as the menu ids. - enum { - FIRST_PENDING = 0x0f000000, - DRAGGING_POINTS = 0x0f000000, - DRAGGING_NEW_POINT = 0x0f000001, - DRAGGING_NEW_LINE_POINT = 0x0f000002, - DRAGGING_NEW_CUBIC_POINT = 0x0f000003, - DRAGGING_NEW_ARC_POINT = 0x0f000004, - DRAGGING_CONSTRAINT = 0x0f000005, - DRAGGING_RADIUS = 0x0f000006, - DRAGGING_NORMAL = 0x0f000007, - DRAGGING_NEW_RADIUS = 0x0f000008, - DRAGGING_MARQUEE = 0x0f000009 - }; - - enum SuggestedConstraint { - SUGGESTED_NONE = 0, - SUGGESTED_HORIZONTAL = Constraint::HORIZONTAL, - SUGGESTED_VERTICAL = Constraint::VERTICAL, + // are noted here. + enum class Pending : uint32_t { + NONE = 0, + COMMAND = 1, + DRAGGING_POINTS = 2, + DRAGGING_NEW_POINT = 3, + DRAGGING_NEW_LINE_POINT = 4, + DRAGGING_NEW_CUBIC_POINT = 5, + DRAGGING_NEW_ARC_POINT = 6, + DRAGGING_CONSTRAINT = 7, + DRAGGING_RADIUS = 8, + DRAGGING_NORMAL = 9, + DRAGGING_NEW_RADIUS = 10, + DRAGGING_MARQUEE = 11, }; struct { - int operation; + Pending operation; + Command command; hRequest request; hEntity point; List points; + List requests; hEntity circle; hEntity normal; hConstraint constraint; const char *description; + Platform::Path filename; - SuggestedConstraint suggestion; + bool hasSuggestion; + Constraint::Type suggestion; } pending; - void ClearPending(void); + void ClearPending(bool scheduleShowTW = true); + bool IsFromPending(hRequest r); + void AddToPending(hRequest r); + void ReplacePending(hRequest before, hRequest after); + // The constraint that is being edited with the on-screen textbox. hConstraint constraintBeingEdited; - SuggestedConstraint SuggestLineConstraint(hRequest lineSegment); + bool SuggestLineConstraint(hRequest lineSegment, ConstraintBase::Type *type); Vector SnapToGrid(Vector p); - bool ConstrainPointByHovered(hEntity pt); - void DeleteTaggedRequests(void); - hRequest AddRequest(int type, bool rememberForUndo); - hRequest AddRequest(int type); + Vector SnapToEntityByScreenPoint(Point2d pp, hEntity he); + bool ConstrainPointByHovered(hEntity pt, const Point2d *projected = NULL); + void DeleteTaggedRequests(); + hRequest AddRequest(Request::Type type, bool rememberForUndo); + hRequest AddRequest(Request::Type type); class ParametricCurve { public: @@ -593,14 +687,14 @@ public: void MakeFromEntity(hEntity he, bool reverse); Vector PointAt(double t); Vector TangentAt(double t); - double LengthForAuto(void); + double LengthForAuto(); - hRequest CreateRequestTrimmedTo(double t, bool extraConstraints, - hEntity orig, hEntity arc, bool arcFinish); + void CreateRequestTrimmedTo(double t, bool reuseOrig, + hEntity orig, hEntity arc, bool arcFinish, bool pointf); void ConstrainPointIfCoincident(hEntity hpt); }; - void MakeTangentArc(void); - void SplitLinesOrCurves(void); + void MakeTangentArc(); + void SplitLinesOrCurves(); hEntity SplitEntity(hEntity he, Vector pinter); hEntity SplitLine(hEntity he, Vector pinter); hEntity SplitCircle(hEntity he, Vector pinter); @@ -609,8 +703,9 @@ public: void RemoveConstraintsForPointBeingDeleted(hEntity hpt); void FixConstraintsForRequestBeingDeleted(hRequest hr); void FixConstraintsForPointBeingDeleted(hEntity hpt); + void EditConstraint(hConstraint constraint); - // The current selection. + // A selected entity. class Selection { public: int tag; @@ -619,19 +714,34 @@ public: hConstraint constraint; bool emphasized; - void Draw(void); + void Draw(bool isHovered, Canvas *canvas); - void Clear(void); - bool IsEmpty(void); + void Clear(); + bool IsEmpty(); bool Equals(Selection *b); - bool HasEndpoints(void); + bool HasEndpoints(); }; + + // A hovered entity, with its location relative to the cursor. + class Hover { + public: + int zIndex; + double distance; + double depth; + Selection selection; + }; + + List hoverList; Selection hover; bool hoverWasSelectedOnMousedown; List selection; + + Selection ChooseFromHoverToSelect(); + Selection ChooseFromHoverToDrag(); void HitTestMakeSelection(Point2d mp); - void ClearSelection(void); - void ClearNonexistentSelectionItems(void); + void ClearSelection(); + void ClearNonexistentSelectionItems(); + /// This structure is filled by a call to GroupSelection(). struct { std::vector point; std::vector entity; @@ -654,9 +764,9 @@ public: int stylables; int constraintLabels; int withEndpoints; - int n; + int n; ///< Number of selected items } gs; - void GroupSelection(void); + void GroupSelection(); bool IsSelected(Selection *s); bool IsSelected(hEntity he); void MakeSelected(hEntity he); @@ -664,47 +774,22 @@ public: void MakeSelected(Selection *s); void MakeUnselected(hEntity he, bool coincidentPointTrick); void MakeUnselected(Selection *s, bool coincidentPointTrick); - void SelectByMarquee(void); - void ClearSuper(void); - - enum { - CMNU_UNSELECT_ALL = 0x100, - CMNU_UNSELECT_HOVERED = 0x101, - CMNU_CUT_SEL = 0x102, - CMNU_COPY_SEL = 0x103, - CMNU_PASTE = 0x104, - CMNU_PASTE_XFRM = 0x105, - CMNU_DELETE_SEL = 0x106, - CMNU_SELECT_CHAIN = 0x107, - CMNU_NEW_CUSTOM_STYLE = 0x110, - CMNU_NO_STYLE = 0x111, - CMNU_GROUP_INFO = 0x120, - CMNU_STYLE_INFO = 0x121, - CMNU_REFERENCE_DIM = 0x130, - CMNU_OTHER_ANGLE = 0x131, - CMNU_DEL_COINCIDENT = 0x132, - CMNU_SNAP_TO_GRID = 0x140, - CMNU_REMOVE_SPLINE_PT = 0x141, - CMNU_ADD_SPLINE_PT = 0x142, - CMNU_FIRST_STYLE = 0x40000000 - }; - void ContextMenuListStyles(void); - int64_t contextMenuCancelTime; + void SelectByMarquee(); + void ClearSuper(); // The toolbar, in toolbar.cpp - bool ToolbarDrawOrHitTest(int x, int y, bool paint, int *menuHit); - void ToolbarDraw(void); + bool ToolbarDrawOrHitTest(int x, int y, UiCanvas *canvas, + Command *hitCommand, int *hitX, int *hitY); + void ToolbarDraw(UiCanvas *canvas); bool ToolbarMouseMoved(int x, int y); bool ToolbarMouseDown(int x, int y); - static void TimerCallback(void); - int toolbarHovered; - int toolbarTooltipped; - int toolbarMouseX, toolbarMouseY; + Command toolbarHovered; // This sets what gets displayed. bool showWorkplanes; bool showNormals; bool showPoints; + bool showConstruction; bool showConstraints; bool showTextWindow; bool showShaded; @@ -712,36 +797,42 @@ public: bool showOutlines; bool showFaces; bool showMesh; - bool showHdnLines; void ToggleBool(bool *v); + enum class DrawOccludedAs { INVISIBLE, STIPPLED, VISIBLE }; + DrawOccludedAs drawOccludedAs; + bool showSnapGrid; + void DrawSnapGrid(Canvas *canvas); void AddPointToDraggedList(hEntity hp); void StartDraggingByEntity(hEntity he); - void StartDraggingBySelection(void); + void StartDraggingBySelection(); void UpdateDraggedNum(Vector *pos, double mx, double my); void UpdateDraggedPoint(hEntity hp, double mx, double my); - // These are called by the platform-specific code. - void Paint(void); + void Invalidate(bool clearPersistent = false); + void DrawEntities(Canvas *canvas, bool persistent); + void DrawPersistent(Canvas *canvas); + void Draw(Canvas *canvas); + void Paint(); + + bool MouseEvent(Platform::MouseEvent event); void MouseMoved(double x, double y, bool leftDown, bool middleDown, - bool rightDown, bool shiftDown, bool ctrlDown); - void MouseLeftDown(double x, double y); - void MouseLeftUp(double x, double y); + bool rightDown, bool shiftDown, bool ctrlDown); + void MouseLeftDown(double x, double y, bool shiftDown, bool ctrlDown); + void MouseLeftUp(double x, double y, bool shiftDown, bool ctrlDown); void MouseLeftDoubleClick(double x, double y); void MouseMiddleOrRightDown(double x, double y); void MouseRightUp(double x, double y); void MouseScroll(double x, double y, int delta); - void MouseLeave(void); - bool KeyDown(int c); - void EditControlDone(const char *s); - - int64_t lastSpaceNavigatorTime; - hGroup lastSpaceNavigatorGroup; - void SpaceNavigatorMoved(double tx, double ty, double tz, - double rx, double ry, double rz, bool shiftDown); - void SpaceNavigatorButtonUp(void); + void MouseLeave(); + bool KeyboardEvent(Platform::KeyboardEvent event); + void EditControlDone(const std::string &s); + + int64_t last6DofTime; + hGroup last6DofGroup; + void SixDofEvent(Platform::SixDofEvent event); }; diff --git a/src/undoredo.cpp b/src/undoredo.cpp index 04b96ee..8ea8a06 100644 --- a/src/undoredo.cpp +++ b/src/undoredo.cpp @@ -7,14 +7,14 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -void SolveSpaceUI::UndoRemember(void) { +void SolveSpaceUI::UndoRemember() { unsaved = true; PushFromCurrentOnto(&undo); UndoClearStack(&redo); UndoEnableMenus(); } -void SolveSpaceUI::UndoUndo(void) { +void SolveSpaceUI::UndoUndo() { if(undo.cnt <= 0) return; PushFromCurrentOnto(&redo); @@ -22,7 +22,7 @@ void SolveSpaceUI::UndoUndo(void) { UndoEnableMenus(); } -void SolveSpaceUI::UndoRedo(void) { +void SolveSpaceUI::UndoRedo() { if(redo.cnt <= 0) return; PushFromCurrentOnto(&undo); @@ -30,14 +30,12 @@ void SolveSpaceUI::UndoRedo(void) { UndoEnableMenus(); } -void SolveSpaceUI::UndoEnableMenus(void) { - EnableMenuById(GraphicsWindow::MNU_UNDO, undo.cnt > 0); - EnableMenuById(GraphicsWindow::MNU_REDO, redo.cnt > 0); +void SolveSpaceUI::UndoEnableMenus() { + SS.GW.undoMenuItem->SetEnabled(undo.cnt > 0); + SS.GW.redoMenuItem->SetEnabled(redo.cnt > 0); } void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) { - int i; - if(uk->cnt == MAX_UNDO) { UndoClearState(&(uk->d[uk->write])); // And then write in to this one again @@ -47,9 +45,10 @@ void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) { UndoState *ut = &(uk->d[uk->write]); *ut = {}; - for(i = 0; i < SK.group.n; i++) { - Group *src = &(SK.group.elem[i]); - Group dest = *src; + ut->group.ReserveMore(SK.group.n); + for(Group &src : SK.group) { + // Shallow copy + Group dest(src); // And then clean up all the stuff that needs to be a deep copy, // and zero out all the dynamic stuff that will get regenerated. dest.clean = false; @@ -63,52 +62,43 @@ void SolveSpaceUI::PushFromCurrentOnto(UndoStack *uk) { dest.thisShell = {}; dest.runningShell = {}; dest.displayMesh = {}; - dest.displayEdges = {}; dest.displayOutlines = {}; - dest.remap = {}; - src->remap.DeepCopyInto(&(dest.remap)); + dest.remap = src.remap; dest.impMesh = {}; dest.impShell = {}; dest.impEntity = {}; ut->group.Add(&dest); } - for(i = 0; i < SK.groupOrder.n; i++) { - ut->groupOrder.Add(&(SK.groupOrder.elem[i])); - } - for(i = 0; i < SK.request.n; i++) { - ut->request.Add(&(SK.request.elem[i])); - } - for(i = 0; i < SK.constraint.n; i++) { - Constraint *src = &(SK.constraint.elem[i]); - Constraint dest = *src; - dest.dogd = {}; + for(auto &src : SK.groupOrder) { ut->groupOrder.Add(&src); } + ut->request.ReserveMore(SK.request.n); + for(auto &src : SK.request) { ut->request.Add(&src); } + ut->constraint.ReserveMore(SK.constraint.n); + for(auto &src : SK.constraint) { + // Shallow copy + Constraint dest(src); ut->constraint.Add(&dest); } - for(i = 0; i < SK.param.n; i++) { - ut->param.Add(&(SK.param.elem[i])); - } - for(i = 0; i < SK.style.n; i++) { - ut->style.Add(&(SK.style.elem[i])); - } + ut->param.ReserveMore(SK.param.n); + for(auto &src : SK.param) { ut->param.Add(&src); } + ut->style.ReserveMore(SK.style.n); + for(auto &src : SK.style) { ut->style.Add(&src); } ut->activeGroup = SS.GW.activeGroup; uk->write = WRAP(uk->write + 1, MAX_UNDO); } void SolveSpaceUI::PopOntoCurrentFrom(UndoStack *uk) { - int i; - - if(uk->cnt <= 0) oops(); + ssassert(uk->cnt > 0, "Cannot pop from empty undo stack"); (uk->cnt)--; uk->write = WRAP(uk->write - 1, MAX_UNDO); UndoState *ut = &(uk->d[uk->write]); // Free everything in the main copy of the program before replacing it - for(i = 0; i < SK.groupOrder.n; i++) { - Group *g = SK.GetGroup(SK.groupOrder.elem[i]); + for(hGroup hg : SK.groupOrder) { + Group *g = SK.GetGroup(hg); g->Clear(); } SK.group.Clear(); @@ -120,8 +110,7 @@ void SolveSpaceUI::PopOntoCurrentFrom(UndoStack *uk) { // And then do a shallow copy of the state from the undo list ut->group.MoveSelfInto(&(SK.group)); - for(i = 0; i < ut->groupOrder.n; i++) - SK.groupOrder.Add(&ut->groupOrder.elem[i]); + for(auto &gh : ut->groupOrder) { SK.groupOrder.Add(&gh); } ut->request.MoveSelfInto(&(SK.request)); ut->constraint.MoveSelfInto(&(SK.constraint)); ut->param.MoveSelfInto(&(SK.param)); @@ -135,8 +124,8 @@ void SolveSpaceUI::PopOntoCurrentFrom(UndoStack *uk) { // sketch just changed a lot. SS.GW.ClearSuper(); SS.TW.ClearSuper(); - SS.ReloadAllImported(); - SS.GenerateAll(SolveSpaceUI::GENERATE_ALL); + SS.ReloadAllLinked(SS.saveFile); + SS.GenerateAll(SolveSpaceUI::Generate::ALL); SS.ScheduleShowTW(); // Activate the group that was active before. @@ -154,12 +143,7 @@ void SolveSpaceUI::UndoClearStack(UndoStack *uk) { } void SolveSpaceUI::UndoClearState(UndoState *ut) { - int i; - for(i = 0; i < ut->group.n; i++) { - Group *g = &(ut->group.elem[i]); - - g->remap.Clear(); - } + for(auto &g : ut->group) { g.remap.clear(); } ut->group.Clear(); ut->request.Clear(); ut->constraint.Clear(); diff --git a/src/unix/gloffscreen.cpp b/src/unix/gloffscreen.cpp deleted file mode 100644 index be836e2..0000000 --- a/src/unix/gloffscreen.cpp +++ /dev/null @@ -1,86 +0,0 @@ -//----------------------------------------------------------------------------- -// Offscreen rendering in OpenGL using framebuffer objects. -// -// Copyright 2015 -//----------------------------------------------------------------------------- -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "gloffscreen.h" -#include "solvespace.h" - -GLOffscreen::GLOffscreen() : _pixels(NULL), _pixels_inv(NULL), _width(0), _height(0) { -#ifndef __APPLE__ - if(glewInit() != GLEW_OK) - oops(); -#endif - - if(!GL_EXT_framebuffer_object) - oops(); - - glGenFramebuffersEXT(1, &_framebuffer); - glGenRenderbuffersEXT(1, &_color_renderbuffer); - glGenRenderbuffersEXT(1, &_depth_renderbuffer); -} - -GLOffscreen::~GLOffscreen() { - delete[] _pixels; - delete[] _pixels_inv; - glDeleteRenderbuffersEXT(1, &_depth_renderbuffer); - glDeleteRenderbuffersEXT(1, &_color_renderbuffer); - glDeleteFramebuffersEXT(1, &_framebuffer); -} - -bool GLOffscreen::begin(int width, int height) { - if(_width != width || _height != height) { - delete[] _pixels; - delete[] _pixels_inv; - - _pixels = new uint32_t[width * height * 4]; - _pixels_inv = new uint32_t[width * height * 4]; - - _width = width; - _height = height; - } - - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _framebuffer); - - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, _color_renderbuffer); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, _width, _height); - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, - GL_RENDERBUFFER_EXT, _color_renderbuffer); - - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, _depth_renderbuffer); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, _width, _height); - glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, - GL_RENDERBUFFER_EXT, _depth_renderbuffer); - - if(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) - return true; - - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); - return false; -} - -uint8_t *GLOffscreen::end(bool flip) { -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - glReadPixels(0, 0, _width, _height, - GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, _pixels_inv); -#else - glReadPixels(0, 0, _width, _height, - GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, _pixels_inv); -#endif - - if(flip) { - /* in OpenGL coordinates, bottom is zero Y */ - for(int i = 0; i < _height; i++) - memcpy(&_pixels[_width * i], &_pixels_inv[_width * (_height - i - 1)], _width * 4); - } - - glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); - - return (uint8_t*) (flip ? _pixels : _pixels_inv); -} diff --git a/src/unix/gloffscreen.h b/src/unix/gloffscreen.h deleted file mode 100644 index c897be3..0000000 --- a/src/unix/gloffscreen.h +++ /dev/null @@ -1,36 +0,0 @@ -//----------------------------------------------------------------------------- -// Offscreen rendering in OpenGL using framebuffer objects. -// -// Copyright 2015 -//----------------------------------------------------------------------------- -#ifndef __GLOFFSCREEN_H -#define __GLOFFSCREEN_H - -#include - -class GLOffscreen { -public: - /* these allocate and deallocate OpenGL resources. - an OpenGL context /must/ be current. */ - GLOffscreen(); - ~GLOffscreen(); - - /* prepare for drawing a frame of specified size. - returns true if OpenGL likes our configuration, false - otherwise. if it returns false, the OpenGL state is restored. */ - bool begin(int width, int height); - - /* get pixels out of the frame and restore OpenGL state. - the pixel format is ARGB32 with top row at index 0 if - flip is true and bottom row at index 0 if flip is false. - the returned array is valid until the next call to begin() */ - uint8_t *end(bool flip = true); - -private: - unsigned int _framebuffer; - unsigned int _color_renderbuffer, _depth_renderbuffer; - uint32_t *_pixels, *_pixels_inv; - int _width, _height; -}; - -#endif diff --git a/src/unix/unixutil.cpp b/src/unix/unixutil.cpp deleted file mode 100644 index 7c1d921..0000000 --- a/src/unix/unixutil.cpp +++ /dev/null @@ -1,112 +0,0 @@ -//----------------------------------------------------------------------------- -// Utility functions used by the Unix port. Notably, our memory allocation; -// we use two separate allocators, one for long-lived stuff and one for -// stuff that gets freed after every regeneration of the model, to save us -// the trouble of freeing the latter explicitly. -// -// Copyright 2008-2013 Jonathan Westhues. -// Copyright 2013 Daniel Richard G. -//----------------------------------------------------------------------------- -#include - -#include "solvespace.h" - -namespace SolveSpace { - -void dbp(const char *str, ...) -{ - va_list f; - static char buf[1024*50]; - va_start(f, str); - vsnprintf(buf, sizeof(buf), str, f); - va_end(f); - - fputs(buf, stderr); - fputc('\n', stderr); -} - -FILE *ssfopen(const std::string &filename, const char *mode) -{ - if(filename.length() != strlen(filename.c_str())) oops(); - return fopen(filename.c_str(), mode); -} - -void ssremove(const std::string &filename) -{ - if(filename.length() != strlen(filename.c_str())) oops(); - remove(filename.c_str()); -} - -int64_t GetUnixTime(void) -{ - time_t ret; - time(&ret); - return (int64_t)ret; -} - -//----------------------------------------------------------------------------- -// A separate heap, on which we allocate expressions. Maybe a bit faster, -// since fragmentation is less of a concern, and it also makes it possible -// to be sloppy with our memory management, and just free everything at once -// at the end. -//----------------------------------------------------------------------------- - -typedef struct _AllocTempHeader AllocTempHeader; - -typedef struct _AllocTempHeader { - AllocTempHeader *prev; - AllocTempHeader *next; -} AllocTempHeader; - -static AllocTempHeader *Head = NULL; - -void *AllocTemporary(size_t n) -{ - AllocTempHeader *h = - (AllocTempHeader *)malloc(n + sizeof(AllocTempHeader)); - h->prev = NULL; - h->next = Head; - if(Head) Head->prev = h; - Head = h; - memset(&h[1], 0, n); - return (void *)&h[1]; -} - -void FreeTemporary(void *p) -{ - AllocTempHeader *h = (AllocTempHeader *)p - 1; - if(h->prev) { - h->prev->next = h->next; - } else { - Head = h->next; - } - if(h->next) h->next->prev = h->prev; - free(h); -} - -void FreeAllTemporary(void) -{ - AllocTempHeader *h = Head; - while(h) { - AllocTempHeader *f = h; - h = h->next; - free(f); - } - Head = NULL; -} - -void *MemAlloc(size_t n) { - void *p = malloc(n); - if(!p) oops(); - return p; -} - -void MemFree(void *p) { - free(p); -} - -void InitHeaps(void) { - /* nothing to do */ -} - -}; diff --git a/src/util.cpp b/src/util.cpp index 847f378..f14417c 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -6,22 +6,32 @@ //----------------------------------------------------------------------------- #include "solvespace.h" +void SolveSpace::AssertFailure(const char *file, unsigned line, const char *function, + const char *condition, const char *message) { + std::string formattedMsg; + formattedMsg += ssprintf("File %s, line %u, function %s:\n", file, line, function); + formattedMsg += ssprintf("Assertion failed: %s.\n", condition); + formattedMsg += ssprintf("Message: %s.\n", message); + SolveSpace::Platform::FatalError(formattedMsg); +} + std::string SolveSpace::ssprintf(const char *fmt, ...) { va_list va; va_start(va, fmt); int size = vsnprintf(NULL, 0, fmt, va); - if(size < 0) oops(); + ssassert(size >= 0, "vsnprintf could not encode string"); va_end(va); std::string result; - result.resize(size); + result.resize(size + 1); va_start(va, fmt); vsnprintf(&result[0], size + 1, fmt, va); va_end(va); + result.resize(size); return result; } @@ -47,45 +57,10 @@ char32_t utf8_iterator::operator*() return result; } -bool SolveSpace::FilenameHasExtension(const std::string &str, const char *ext) -{ - int i, ls = str.length(), le = strlen(ext); - - if(ls < le) return false; - - for(i = 0; i < le; i++) { - if(tolower(ext[le-i-1]) != tolower(str[ls-i-1])) { - return false; - } - } - return true; -} - -bool SolveSpace::ReadFile(const std::string &filename, std::string *data) -{ - FILE *f = ssfopen(filename.c_str(), "rb"); - if(f == NULL) - return false; - - fseek(f, 0, SEEK_END); - data->resize(ftell(f)); - fseek(f, 0, SEEK_SET); - fread(&(*data)[0], 1, data->size(), f); - fclose(f); - - return true; -} - -bool SolveSpace::WriteFile(const std::string &filename, const std::string &data) +int64_t SolveSpace::GetMilliseconds() { - FILE *f = ssfopen(filename.c_str(), "wb"); - if(f == NULL) - return false; - - fwrite(&data[0], 1, data.size(), f); - fclose(f); - - return true; + auto timestamp = std::chrono::steady_clock::now().time_since_epoch(); + return std::chrono::duration_cast(timestamp).count(); } void SolveSpace::MakeMatrix(double *mat, @@ -112,91 +87,118 @@ void SolveSpace::MakeMatrix(double *mat, mat[15] = a44; } +void SolveSpace::MultMatrix(double *mata, double *matb, double *matr) { + for(int i = 0; i < 4; i++) { + for(int j = 0; j < 4; j++) { + double s = 0.0; + for(int k = 0; k < 4; k++) { + s += mata[k * 4 + j] * matb[i * 4 + k]; + } + matr[i * 4 + j] = s; + } + } +} + //----------------------------------------------------------------------------- -// Word-wrap the string for our message box appropriately, and then display +// Format the string for our message box appropriately, and then display // that string. //----------------------------------------------------------------------------- -static void DoStringForMessageBox(const char *str, va_list f, bool error) +static void MessageBox(const char *fmt, va_list va, bool error, + std::function onDismiss = std::function()) { - char inBuf[1024*50]; - vsprintf(inBuf, str, f); - - char outBuf[1024*50]; - int i = 0, j = 0, len = 0, longestLen = 47; - int rows = 0, cols = 0; - - // Count the width of the longest line that starts with spaces; those - // are list items, that should not be split in the middle. - bool listLine = false; - while(inBuf[i]) { - if(inBuf[i] == '\r') { - // ignore these - } else if(inBuf[i] == ' ' && len == 0) { - listLine = true; - } else if(inBuf[i] == '\n') { - if(listLine) longestLen = max(longestLen, len); - len = 0; - } else { - len++; +#ifndef LIBRARY + va_list va_size; + va_copy(va_size, va); + int size = vsnprintf(NULL, 0, fmt, va_size); + ssassert(size >= 0, "vsnprintf could not encode string"); + va_end(va_size); + + std::string text; + text.resize(size); + + vsnprintf(&text[0], size + 1, fmt, va); + + // Split message text using a heuristic for better presentation. + size_t separatorAt = 0; + while(separatorAt != std::string::npos) { + size_t dotAt = text.find('.', separatorAt + 1), + colonAt = text.find(':', separatorAt + 1); + separatorAt = min(dotAt, colonAt); + if(separatorAt == std::string::npos || + (separatorAt + 1 < text.size() && isspace(text[separatorAt + 1]))) { + break; + } + } + std::string message = text; + std::string description; + if(separatorAt != std::string::npos) { + message = text.substr(0, separatorAt + 1); + if(separatorAt + 1 < text.size()) { + description = text.substr(separatorAt + 1); } - i++; } - if(listLine) longestLen = max(longestLen, len); - - // Word wrap according to our target line length longestLen. - len = 0; - i = 0; - while(inBuf[i]) { - if(inBuf[i] == '\r') { - // ignore these - } else if(inBuf[i] == '\n') { - outBuf[j++] = '\n'; - if(len == 0) rows++; - len = 0; - } else if(inBuf[i] == ' ' && len > longestLen) { - outBuf[j++] = '\n'; - len = 0; + + if(description.length() > 0) { + std::string::iterator it = description.begin(); + while(isspace(*it)) it++; + description = description.substr(it - description.begin()); + } + + Platform::MessageDialogRef dialog = CreateMessageDialog(SS.GW.window); + if (!dialog) { + if (error) { + fprintf(stderr, "Error: %s\n", message.c_str()); } else { - outBuf[j++] = inBuf[i]; - // Count rows when we draw the first character; so an empty - // row doesn't end up counting. - if(len == 0) rows++; - len++; + fprintf(stderr, "Message: %s\n", message.c_str()); } - cols = max(cols, len); - i++; + if(onDismiss) { + onDismiss(); + } + return; + } + using Platform::MessageDialog; + if(error) { + dialog->SetType(MessageDialog::Type::ERROR); + } else { + dialog->SetType(MessageDialog::Type::INFORMATION); + } + dialog->SetTitle(error ? C_("title", "Error") : C_("title", "Message")); + dialog->SetMessage(message); + if(!description.empty()) { + dialog->SetDescription(description); } - outBuf[j++] = '\0'; + dialog->AddButton(C_("button", "&OK"), MessageDialog::Response::OK, + /*isDefault=*/true); - // And then display the text with our actual longest line length. - DoMessageBox(outBuf, rows, cols, error); + dialog->onResponse = [=](MessageDialog::Response _response) { + if(onDismiss) { + onDismiss(); + } + }; + dialog->ShowModal(); +#endif } -void SolveSpace::Error(const char *str, ...) +void SolveSpace::Error(const char *fmt, ...) { va_list f; - va_start(f, str); - DoStringForMessageBox(str, f, true); + va_start(f, fmt); + MessageBox(fmt, f, /*error=*/true); va_end(f); } -void SolveSpace::Message(const char *str, ...) +void SolveSpace::Message(const char *fmt, ...) { va_list f; - va_start(f, str); - DoStringForMessageBox(str, f, false); + va_start(f, fmt); + MessageBox(fmt, f, /*error=*/false); + va_end(f); +} +void SolveSpace::MessageAndRun(std::function onDismiss, const char *fmt, ...) +{ + va_list f; + va_start(f, fmt); + MessageBox(fmt, f, /*error=*/false, onDismiss); va_end(f); } - -void SolveSpace::CnfFreezeBool(bool v, const std::string &name) - { CnfFreezeInt(v ? 1 : 0, name); } - -void SolveSpace::CnfFreezeColor(RgbaColor v, const std::string &name) - { CnfFreezeInt(v.ToPackedInt(), name); } - -bool SolveSpace::CnfThawBool(bool v, const std::string &name) - { return CnfThawInt(v ? 1 : 0, name) != 0; } - -RgbaColor SolveSpace::CnfThawColor(RgbaColor v, const std::string &name) - { return RgbaColor::FromPackedInt(CnfThawInt(v.ToPackedInt(), name)); } //----------------------------------------------------------------------------- // Solve a mostly banded matrix. In a given row, there are LEFT_OF_DIAG @@ -205,7 +207,7 @@ RgbaColor SolveSpace::CnfThawColor(RgbaColor v, const std::string &name) // There also may be elements in the last two columns of any row. We solve // without pivoting. //----------------------------------------------------------------------------- -void BandedMatrix::Solve(void) { +void BandedMatrix::Solve() { int i, ip, j, jp; double temp; @@ -306,7 +308,7 @@ Quaternion Quaternion::From(Vector u, Vector v) return q.WithMagnitude(1); } -Quaternion Quaternion::Plus(Quaternion b) { +Quaternion Quaternion::Plus(Quaternion b) const { Quaternion q; q.w = w + b.w; q.vx = vx + b.vx; @@ -315,7 +317,7 @@ Quaternion Quaternion::Plus(Quaternion b) { return q; } -Quaternion Quaternion::Minus(Quaternion b) { +Quaternion Quaternion::Minus(Quaternion b) const { Quaternion q; q.w = w - b.w; q.vx = vx - b.vx; @@ -324,7 +326,7 @@ Quaternion Quaternion::Minus(Quaternion b) { return q; } -Quaternion Quaternion::ScaledBy(double s) { +Quaternion Quaternion::ScaledBy(double s) const { Quaternion q; q.w = w*s; q.vx = vx*s; @@ -333,15 +335,15 @@ Quaternion Quaternion::ScaledBy(double s) { return q; } -double Quaternion::Magnitude(void) { +double Quaternion::Magnitude() const { return sqrt(w*w + vx*vx + vy*vy + vz*vz); } -Quaternion Quaternion::WithMagnitude(double s) { +Quaternion Quaternion::WithMagnitude(double s) const { return ScaledBy(s/Magnitude()); } -Vector Quaternion::RotationU(void) { +Vector Quaternion::RotationU() const { Vector v; v.x = w*w + vx*vx - vy*vy - vz*vz; v.y = 2*w *vz + 2*vx*vy; @@ -349,7 +351,7 @@ Vector Quaternion::RotationU(void) { return v; } -Vector Quaternion::RotationV(void) { +Vector Quaternion::RotationV() const { Vector v; v.x = 2*vx*vy - 2*w*vz; v.y = w*w - vx*vx + vy*vy - vz*vz; @@ -357,7 +359,7 @@ Vector Quaternion::RotationV(void) { return v; } -Vector Quaternion::RotationN(void) { +Vector Quaternion::RotationN() const { Vector v; v.x = 2*w*vy + 2*vx*vz; v.y = 2*vy*vz - 2*w*vx; @@ -365,14 +367,14 @@ Vector Quaternion::RotationN(void) { return v; } -Vector Quaternion::Rotate(Vector p) { +Vector Quaternion::Rotate(Vector p) const { // Express the point in the new basis return (RotationU().ScaledBy(p.x)).Plus( RotationV().ScaledBy(p.y)).Plus( RotationN().ScaledBy(p.z)); } -Quaternion Quaternion::Inverse(void) { +Quaternion Quaternion::Inverse() const { Quaternion r; r.w = w; r.vx = -vx; @@ -381,7 +383,7 @@ Quaternion Quaternion::Inverse(void) { return r.WithMagnitude(1); // not that the normalize should be reqd } -Quaternion Quaternion::ToThe(double p) { +Quaternion Quaternion::ToThe(double p) const { // Avoid division by zero, or arccos of something not in its domain if(w >= (1 - 1e-6)) { return From(1, 0, 0, 0); @@ -401,7 +403,7 @@ Quaternion Quaternion::ToThe(double p) { return r; } -Quaternion Quaternion::Times(Quaternion b) { +Quaternion Quaternion::Times(Quaternion b) const { double sa = w, sb = b.w; Vector va = { vx, vy, vz }; Vector vb = { b.vx, b.vy, b.vz }; @@ -417,7 +419,7 @@ Quaternion Quaternion::Times(Quaternion b) { return r; } -Quaternion Quaternion::Mirror(void) { +Quaternion Quaternion::Mirror() const { Vector u = RotationU(), v = RotationV(); u = u.ScaledBy(-1); @@ -426,12 +428,6 @@ Quaternion Quaternion::Mirror(void) { } -Vector Vector::From(double x, double y, double z) { - Vector v; - v.x = x; v.y = y; v.z = z; - return v; -} - Vector Vector::From(hParam x, hParam y, hParam z) { Vector v; v.x = SK.GetParam(x)->val; @@ -440,81 +436,19 @@ Vector Vector::From(hParam x, hParam y, hParam z) { return v; } -double Vector::Element(int i) { - switch(i) { - case 0: return x; - case 1: return y; - case 2: return z; - default: oops(); - } -} - -bool Vector::Equals(Vector v, double tol) { - // Quick axis-aligned tests before going further - double dx = v.x - x; if(dx < -tol || dx > tol) return false; - double dy = v.y - y; if(dy < -tol || dy > tol) return false; - double dz = v.z - z; if(dz < -tol || dz > tol) return false; - - return (this->Minus(v)).MagSquared() < tol*tol; -} - -bool Vector::EqualsExactly(Vector v) { +bool Vector::EqualsExactly(Vector v) const { return EXACT(x == v.x && y == v.y && z == v.z); } -Vector Vector::Plus(Vector b) { - Vector r; - - r.x = x + b.x; - r.y = y + b.y; - r.z = z + b.z; - - return r; -} - -Vector Vector::Minus(Vector b) { - Vector r; - - r.x = x - b.x; - r.y = y - b.y; - r.z = z - b.z; - - return r; -} - -Vector Vector::Negated(void) { - Vector r; - - r.x = -x; - r.y = -y; - r.z = -z; - - return r; -} - -Vector Vector::Cross(Vector b) { - Vector r; - - r.x = -(z*b.y) + (y*b.z); - r.y = (z*b.x) - (x*b.z); - r.z = -(y*b.x) + (x*b.y); - - return r; -} - -double Vector::Dot(Vector b) { - return (x*b.x + y*b.y + z*b.z); -} - -double Vector::DirectionCosineWith(Vector b) { +double Vector::DirectionCosineWith(Vector b) const { Vector a = this->WithMagnitude(1); b = b.WithMagnitude(1); return a.Dot(b); } -Vector Vector::Normal(int which) { +Vector Vector::Normal(int which) const { Vector n; // Arbitrarily choose one vector that's normal to us, pivoting @@ -541,20 +475,20 @@ Vector Vector::Normal(int which) { // That's the vector we return. } else if(which == 1) { n = this->Cross(n); - } else oops(); + } else ssassert(false, "Unexpected vector normal index"); n = n.WithMagnitude(1); return n; } -Vector Vector::RotatedAbout(Vector orig, Vector axis, double theta) { +Vector Vector::RotatedAbout(Vector orig, Vector axis, double theta) const { Vector r = this->Minus(orig); r = r.RotatedAbout(axis, theta); return r.Plus(orig); } -Vector Vector::RotatedAbout(Vector axis, double theta) { +Vector Vector::RotatedAbout(Vector axis, double theta) const { double c = cos(theta); double s = sin(theta); @@ -577,7 +511,7 @@ Vector Vector::RotatedAbout(Vector axis, double theta) { return r; } -Vector Vector::DotInToCsys(Vector u, Vector v, Vector n) { +Vector Vector::DotInToCsys(Vector u, Vector v, Vector n) const { Vector r = { this->Dot(u), this->Dot(v), @@ -586,7 +520,7 @@ Vector Vector::DotInToCsys(Vector u, Vector v, Vector n) { return r; } -Vector Vector::ScaleOutOfCsys(Vector u, Vector v, Vector n) { +Vector Vector::ScaleOutOfCsys(Vector u, Vector v, Vector n) const { Vector r = u.ScaledBy(x).Plus( v.ScaledBy(y).Plus( n.ScaledBy(z))); @@ -594,7 +528,7 @@ Vector Vector::ScaleOutOfCsys(Vector u, Vector v, Vector n) { } Vector Vector::InPerspective(Vector u, Vector v, Vector n, - Vector origin, double cameraTan) + Vector origin, double cameraTan) const { Vector r = this->Minus(origin); r = r.DotInToCsys(u, v, n); @@ -606,12 +540,16 @@ Vector Vector::InPerspective(Vector u, Vector v, Vector n, return r; } -double Vector::DistanceToLine(Vector p0, Vector dp) { +double Vector::DistanceToLine(Vector p0, Vector dp) const { double m = dp.Magnitude(); return ((this->Minus(p0)).Cross(dp)).Magnitude() / m; } -bool Vector::OnLineSegment(Vector a, Vector b, double tol) { +double Vector::DistanceToPlane(Vector normal, Vector origin) const { + return this->Dot(normal) - origin.Dot(normal); +} + +bool Vector::OnLineSegment(Vector a, Vector b, double tol) const { if(this->Equals(a, tol) || this->Equals(b, tol)) return true; Vector d = b.Minus(a); @@ -621,13 +559,13 @@ bool Vector::OnLineSegment(Vector a, Vector b, double tol) { if(distsq >= tol*tol) return false; - double t = (this->Minus(a)).DivPivoting(d); + double t = (this->Minus(a)).DivProjected(d); // On-endpoint already tested if(t < 0 || t > 1) return false; return true; } -Vector Vector::ClosestPointOnLine(Vector p0, Vector dp) { +Vector Vector::ClosestPointOnLine(Vector p0, Vector dp) const { dp = dp.WithMagnitude(1); // this, p0, and (p0+dp) define a plane; the min distance is in // that plane, so calculate its normal @@ -641,25 +579,7 @@ Vector Vector::ClosestPointOnLine(Vector p0, Vector dp) { return this->Plus(n.WithMagnitude(d)); } -double Vector::MagSquared(void) { - return x*x + y*y + z*z; -} - -double Vector::Magnitude(void) { - return sqrt(x*x + y*y + z*z); -} - -Vector Vector::ScaledBy(double v) { - Vector r; - - r.x = x * v; - r.y = y * v; - r.z = z * v; - - return r; -} - -Vector Vector::WithMagnitude(double v) { +Vector Vector::WithMagnitude(double v) const { double m = Magnitude(); if(EXACT(m == 0)) { // We can do a zero vector with zero magnitude, but not any other cases. @@ -672,7 +592,7 @@ Vector Vector::WithMagnitude(double v) { } } -Vector Vector::ProjectVectorInto(hEntity wrkpl) { +Vector Vector::ProjectVectorInto(hEntity wrkpl) const { EntityBase *w = SK.GetEntity(wrkpl); Vector u = w->Normal()->NormalU(); Vector v = w->Normal()->NormalV(); @@ -683,7 +603,7 @@ Vector Vector::ProjectVectorInto(hEntity wrkpl) { return (u.ScaledBy(up)).Plus(v.ScaledBy(vp)); } -Vector Vector::ProjectInto(hEntity wrkpl) { +Vector Vector::ProjectInto(hEntity wrkpl) const { EntityBase *w = SK.GetEntity(wrkpl); Vector p0 = w->WorkplaneGetOffset(); @@ -692,37 +612,30 @@ Vector Vector::ProjectInto(hEntity wrkpl) { return p0.Plus(f.ProjectVectorInto(wrkpl)); } -Point2d Vector::Project2d(Vector u, Vector v) { +Point2d Vector::Project2d(Vector u, Vector v) const { Point2d p; p.x = this->Dot(u); p.y = this->Dot(v); return p; } -Point2d Vector::ProjectXy(void) { +Point2d Vector::ProjectXy() const { Point2d p; p.x = x; p.y = y; return p; } -Vector4 Vector::Project4d(void) { +Vector4 Vector::Project4d() const { return Vector4::From(1, x, y, z); } -double Vector::DivPivoting(Vector delta) { - double mx = fabs(delta.x), my = fabs(delta.y), mz = fabs(delta.z); - - if(mx > my && mx > mz) { - return x/delta.x; - } else if(my > mz) { - return y/delta.y; - } else { - return z/delta.z; - } +double Vector::DivProjected(Vector delta) const { + return (x*delta.x + y*delta.y + z*delta.z) + / (delta.x*delta.x + delta.y*delta.y + delta.z*delta.z); } -Vector Vector::ClosestOrtho(void) { +Vector Vector::ClosestOrtho() const { double mx = fabs(x), my = fabs(y), mz = fabs(z); if(mx > my && mx > mz) { @@ -734,7 +647,7 @@ Vector Vector::ClosestOrtho(void) { } } -Vector Vector::ClampWithin(double minv, double maxv) { +Vector Vector::ClampWithin(double minv, double maxv) const { Vector ret = *this; if(ret.x < minv) ret.x = minv; @@ -748,17 +661,7 @@ Vector Vector::ClampWithin(double minv, double maxv) { return ret; } -void Vector::MakeMaxMin(Vector *maxv, Vector *minv) { - maxv->x = max(maxv->x, x); - maxv->y = max(maxv->y, y); - maxv->z = max(maxv->z, z); - - minv->x = min(minv->x, x); - minv->y = min(minv->y, y); - minv->z = min(minv->z, z); -} - -bool Vector::OutsideAndNotOn(Vector maxv, Vector minv) { +bool Vector::OutsideAndNotOn(Vector maxv, Vector minv) const { return (x > maxv.x + LENGTH_EPS) || (x < minv.x - LENGTH_EPS) || (y > maxv.y + LENGTH_EPS) || (y < minv.y - LENGTH_EPS) || (z > maxv.z + LENGTH_EPS) || (z < minv.z - LENGTH_EPS); @@ -776,7 +679,7 @@ bool Vector::BoundingBoxesDisjoint(Vector amax, Vector amin, } bool Vector::BoundingBoxIntersectsLine(Vector amax, Vector amin, - Vector p0, Vector p1, bool segment) + Vector p0, Vector p1, bool asSegment) { Vector dp = p1.Minus(p0); double lp = dp.Magnitude(); @@ -794,7 +697,7 @@ bool Vector::BoundingBoxIntersectsLine(Vector amax, Vector amin, double t = (d - p0.Element(i)) / dp.Element(i); Vector p = p0.Plus(dp.ScaledBy(t)); - if(segment && (t < -LENGTH_EPS || t > (lp+LENGTH_EPS))) continue; + if(asSegment && (t < -LENGTH_EPS || t > (lp+LENGTH_EPS))) continue; if(p.Element(j) > amax.Element(j) + LENGTH_EPS) continue; if(p.Element(k) > amax.Element(k) + LENGTH_EPS) continue; @@ -928,6 +831,25 @@ Vector Vector::AtIntersectionOfPlanes(Vector na, double da, return Vector::From(detx/det, dety/det, detz/det); } +size_t VectorHash::operator()(const Vector &v) const { + const size_t size = (size_t)pow(std::numeric_limits::max(), 1.0 / 3.0) - 1; + const double eps = 4.0 * LENGTH_EPS; + + double x = fabs(v.x) / eps; + double y = fabs(v.y) / eps; + double z = fabs(v.y) / eps; + + size_t xs = size_t(fmod(x, (double)size)); + size_t ys = size_t(fmod(y, (double)size)); + size_t zs = size_t(fmod(z, (double)size)); + + return (zs * size + ys) * size + xs; +} + +bool VectorPred::operator()(Vector a, Vector b) const { + return a.Equals(b, LENGTH_EPS); +} + Vector4 Vector4::From(double w, double x, double y, double z) { Vector4 ret; ret.w = w; @@ -945,19 +867,19 @@ Vector4 Vector4::Blend(Vector4 a, Vector4 b, double t) { return (a.ScaledBy(1 - t)).Plus(b.ScaledBy(t)); } -Vector4 Vector4::Plus(Vector4 b) { +Vector4 Vector4::Plus(Vector4 b) const { return Vector4::From(w + b.w, x + b.x, y + b.y, z + b.z); } -Vector4 Vector4::Minus(Vector4 b) { +Vector4 Vector4::Minus(Vector4 b) const { return Vector4::From(w - b.w, x - b.x, y - b.y, z - b.z); } -Vector4 Vector4::ScaledBy(double s) { +Vector4 Vector4::ScaledBy(double s) const { return Vector4::From(w*s, x*s, y*s, z*s); } -Vector Vector4::PerspectiveProject(void) { +Vector Vector4::PerspectiveProject() const { return Vector::From(x / w, y / w, z / w); } @@ -965,6 +887,19 @@ Point2d Point2d::From(double x, double y) { return { x, y }; } +Point2d Point2d::FromPolar(double r, double a) { + return { r * cos(a), r * sin(a) }; +} + +double Point2d::Angle() const { + double a = atan2(y, x); + return M_PI + remainder(a - M_PI, 2 * M_PI); +} + +double Point2d::AngleTo(const Point2d &p) const { + return p.Minus(*this).Angle(); +} + Point2d Point2d::Plus(const Point2d &b) const { return { x + b.x, y + b.y }; } @@ -977,19 +912,15 @@ Point2d Point2d::ScaledBy(double s) const { return { x * s, y * s }; } -double Point2d::DivPivoting(Point2d delta) const { - if(fabs(delta.x) > fabs(delta.y)) { - return x/delta.x; - } else { - return y/delta.y; - } +double Point2d::DivProjected(Point2d delta) const { + return (x*delta.x + y*delta.y) / (delta.x*delta.x + delta.y*delta.y); } -double Point2d::MagSquared(void) const { +double Point2d::MagSquared() const { return x*x + y*y; } -double Point2d::Magnitude(void) const { +double Point2d::Magnitude() const { return sqrt(x*x + y*y); } @@ -1012,26 +943,39 @@ double Point2d::Dot(Point2d p) const { return x*p.x + y*p.y; } -double Point2d::DistanceToLine(const Point2d &p0, const Point2d &dp, bool segment) const { +double Point2d::DistanceToLine(const Point2d &p0, const Point2d &dp, bool asSegment) const { double m = dp.x*dp.x + dp.y*dp.y; if(m < LENGTH_EPS*LENGTH_EPS) return VERY_POSITIVE; // Let our line be p = p0 + t*dp, for a scalar t from 0 to 1 double t = (dp.x*(x - p0.x) + dp.y*(y - p0.y))/m; - if((t < 0 || t > 1) && segment) { - // The closest point is one of the endpoints; determine which. - double d0 = DistanceTo(p0); - double d1 = DistanceTo(p0.Plus(dp)); + if(asSegment) { + if(t < 0.0) return DistanceTo(p0); + if(t > 1.0) return DistanceTo(p0.Plus(dp)); + } + Point2d closest = p0.Plus(dp.ScaledBy(t)); + return DistanceTo(closest); +} - return min(d1, d0); - } else { - Point2d closest = p0.Plus(dp.ScaledBy(t)); - return DistanceTo(closest); +double Point2d::DistanceToLineSigned(const Point2d &p0, const Point2d &dp, bool asSegment) const { + double m = dp.x*dp.x + dp.y*dp.y; + if(m < LENGTH_EPS*LENGTH_EPS) return VERY_POSITIVE; + + Point2d n = dp.Normal().WithMagnitude(1.0); + double dist = n.Dot(*this) - n.Dot(p0); + if(asSegment) { + // Let our line be p = p0 + t*dp, for a scalar t from 0 to 1 + double t = (dp.x*(x - p0.x) + dp.y*(y - p0.y))/m; + double sign = (dist > 0.0) ? 1.0 : -1.0; + if(t < 0.0) return DistanceTo(p0) * sign; + if(t > 1.0) return DistanceTo(p0.Plus(dp)) * sign; } + + return dist; } -Point2d Point2d::Normal(void) const { +Point2d Point2d::Normal() const { return { y, -x }; } @@ -1054,8 +998,8 @@ BBox BBox::From(const Vector &p0, const Vector &p1) { return bbox; } -Vector BBox::GetOrigin() { return minp.Plus(maxp.Minus(minp)).ScaledBy(0.5); } -Vector BBox::GetExtents() { return maxp.Minus(minp).ScaledBy(0.5); } +Vector BBox::GetOrigin() const { return minp.Plus(maxp.Minus(minp).ScaledBy(0.5)); } +Vector BBox::GetExtents() const { return maxp.Minus(minp).ScaledBy(0.5); } void BBox::Include(const Vector &v, double r) { minp.x = min(minp.x, v.x - r); @@ -1067,14 +1011,62 @@ void BBox::Include(const Vector &v, double r) { maxp.z = max(maxp.z, v.z + r); } -bool BBox::Overlaps(BBox &b1) { - +bool BBox::Overlaps(const BBox &b1) const { Vector t = b1.GetOrigin().Minus(GetOrigin()); Vector e = b1.GetExtents().Plus(GetExtents()); return fabs(t.x) < e.x && fabs(t.y) < e.y && fabs(t.z) < e.z; } -bool BBox::Contains(const Point2d &p) { - return p.x >= minp.x && p.y >= minp.y && p.x <= maxp.x && p.y <= maxp.y; +bool BBox::Contains(const Point2d &p, double r) const { + return p.x >= (minp.x - r) && + p.y >= (minp.y - r) && + p.x <= (maxp.x + r) && + p.y <= (maxp.y + r); +} + +const std::vector& SolveSpace::StipplePatternDashes(StipplePattern pattern) { + static bool initialized; + static std::vector dashes[(size_t)StipplePattern::LAST + 1]; + if(!initialized) { + // Inkscape ignores all elements that are exactly zero instead of drawing + // them as dots, so set those to 1e-6. + dashes[(size_t)StipplePattern::CONTINUOUS] = + {}; + dashes[(size_t)StipplePattern::SHORT_DASH] = + { 1.0, 2.0 }; + dashes[(size_t)StipplePattern::DASH] = + { 1.0, 1.0 }; + dashes[(size_t)StipplePattern::DASH_DOT] = + { 1.0, 0.5, 1e-6, 0.5 }; + dashes[(size_t)StipplePattern::DASH_DOT_DOT] = + { 1.0, 0.5, 1e-6, 0.5, 1e-6, 0.5 }; + dashes[(size_t)StipplePattern::DOT] = + { 1e-6, 0.5 }; + dashes[(size_t)StipplePattern::LONG_DASH] = + { 2.0, 0.5 }; + dashes[(size_t)StipplePattern::FREEHAND] = + { 1.0, 2.0 }; + dashes[(size_t)StipplePattern::ZIGZAG] = + { 1.0, 2.0 }; + } + + return dashes[(size_t)pattern]; +} + +double SolveSpace::StipplePatternLength(StipplePattern pattern) { + static bool initialized; + static double lengths[(size_t)StipplePattern::LAST + 1]; + if(!initialized) { + for(size_t i = 0; i < (size_t)StipplePattern::LAST; i++) { + const std::vector &dashes = StipplePatternDashes((StipplePattern)i); + double length = 0.0; + for(double dash : dashes) { + length += dash; + } + lengths[i] = length; + } + } + + return lengths[(size_t)pattern]; } diff --git a/src/view.cpp b/src/view.cpp index bf76ba9..14d09ba 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -6,7 +6,7 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -void TextWindow::ShowEditView(void) { +void TextWindow::ShowEditView() { Printf(true, "%Ft3D VIEW PARAMETERS%E"); Printf(true, "%Bd %Ftoverall scale factor%E"); @@ -14,6 +14,8 @@ void TextWindow::ShowEditView(void) { SS.GW.scale * SS.MmPerUnit(), SS.UnitName(), &ScreenChangeViewScale); + Printf(false, "%Bd %Fl%Ll%fset to full scale%E", + &ScreenChangeViewToFullScale); Printf(false, ""); Printf(false, "%Bd %Ftorigin (maps to center of screen)%E"); @@ -38,10 +40,14 @@ void TextWindow::ShowEditView(void) { } void TextWindow::ScreenChangeViewScale(int link, uint32_t v) { - SS.TW.edit.meaning = EDIT_VIEW_SCALE; + SS.TW.edit.meaning = Edit::VIEW_SCALE; SS.TW.ShowEditControl(3, ssprintf("%.3f", SS.GW.scale * SS.MmPerUnit())); } +void TextWindow::ScreenChangeViewToFullScale(int link, uint32_t v) { + SS.GW.scale = SS.GW.window->GetPixelDensity() / 25.4; +} + void TextWindow::ScreenChangeViewOrigin(int link, uint32_t v) { std::string edit_value = ssprintf("%s, %s, %s", @@ -49,54 +55,54 @@ void TextWindow::ScreenChangeViewOrigin(int link, uint32_t v) { SS.MmToString(-SS.GW.offset.y).c_str(), SS.MmToString(-SS.GW.offset.z).c_str()); - SS.TW.edit.meaning = EDIT_VIEW_ORIGIN; + SS.TW.edit.meaning = Edit::VIEW_ORIGIN; SS.TW.ShowEditControl(3, edit_value); } void TextWindow::ScreenChangeViewProjection(int link, uint32_t v) { std::string edit_value = ssprintf("%.3f, %.3f, %.3f", CO(SS.GW.projRight)); - SS.TW.edit.meaning = EDIT_VIEW_PROJ_RIGHT; + SS.TW.edit.meaning = Edit::VIEW_PROJ_RIGHT; SS.TW.ShowEditControl(10, edit_value); } -bool TextWindow::EditControlDoneForView(const char *s) { +bool TextWindow::EditControlDoneForView(const std::string &s) { switch(edit.meaning) { - case EDIT_VIEW_SCALE: { - Expr *e = Expr::From(s, true); + case Edit::VIEW_SCALE: { + Expr *e = Expr::From(s, /*popUpError=*/true); if(e) { double v = e->Eval() / SS.MmPerUnit(); if(v > LENGTH_EPS) { SS.GW.scale = v; } else { - Error("Scale cannot be zero or negative."); + Error(_("Scale cannot be zero or negative.")); } } break; } - case EDIT_VIEW_ORIGIN: { + case Edit::VIEW_ORIGIN: { Vector pt; - if(sscanf(s, "%lf, %lf, %lf", &pt.x, &pt.y, &pt.z) == 3) { + if(sscanf(s.c_str(), "%lf, %lf, %lf", &pt.x, &pt.y, &pt.z) == 3) { pt = pt.ScaledBy(SS.MmPerUnit()); SS.GW.offset = pt.ScaledBy(-1); } else { - Error("Bad format: specify x, y, z"); + Error(_("Bad format: specify x, y, z")); } break; } - case EDIT_VIEW_PROJ_RIGHT: - case EDIT_VIEW_PROJ_UP: { + case Edit::VIEW_PROJ_RIGHT: + case Edit::VIEW_PROJ_UP: { Vector pt; - if(sscanf(s, "%lf, %lf, %lf", &pt.x, &pt.y, &pt.z) != 3) { - Error("Bad format: specify x, y, z"); + if(sscanf(s.c_str(), "%lf, %lf, %lf", &pt.x, &pt.y, &pt.z) != 3) { + Error(_("Bad format: specify x, y, z")); break; } - if(edit.meaning == EDIT_VIEW_PROJ_RIGHT) { + if(edit.meaning == Edit::VIEW_PROJ_RIGHT) { SS.GW.projRight = pt; SS.GW.NormalizeProjectionVectors(); - edit.meaning = EDIT_VIEW_PROJ_UP; + edit.meaning = Edit::VIEW_PROJ_UP; HideEditControl(); ShowEditControl(10, ssprintf("%.3f, %.3f, %.3f", CO(SS.GW.projUp)), editControl.halfRow + 2); diff --git a/src/win32/resource.rc b/src/win32/resource.rc deleted file mode 100644 index e402f8b..0000000 --- a/src/win32/resource.rc +++ /dev/null @@ -1,6 +0,0 @@ - -// we need a manifest if we want visual styles; put in numbers since somethings a bit screwy -// with my SDK install (I don't think I've got *.rh right) -1 24 "manifest.xml" - -4000 ICON "icon.ico" diff --git a/src/win32/w32main.cpp b/src/win32/w32main.cpp deleted file mode 100644 index 562499b..0000000 --- a/src/win32/w32main.cpp +++ /dev/null @@ -1,1479 +0,0 @@ -//----------------------------------------------------------------------------- -// Our WinMain() functions, and Win32-specific stuff to set up our windows -// and otherwise handle our interface to the operating system. Everything -// outside win32/... should be standard C++ and gl. -// -// Copyright 2008-2013 Jonathan Westhues. -//----------------------------------------------------------------------------- -#include -#include -#include -#include -#include - -#include "solvespace.h" -#include "config.h" - -#ifdef HAVE_SPACEWARE -# include -# include -# undef uint32_t // thanks but no thanks -#endif - -HINSTANCE Instance; - -HWND TextWnd; -HWND TextWndScrollBar; -HWND TextEditControl; -HGLRC TextGl; - -HWND GraphicsWnd; -HGLRC GraphicsGl; -HWND GraphicsEditControl; -static struct { - int x, y; -} LastMousePos; - -HMENU SubMenus[100]; -HMENU RecentOpenMenu, RecentImportMenu; - -HMENU ContextMenu, ContextSubmenu; - -int ClientIsSmallerBy; - -HFONT FixedFont; - -#ifdef HAVE_SPACEWARE -// The 6-DOF input device. -SiHdl SpaceNavigator = SI_NO_HANDLE; -#endif - -//----------------------------------------------------------------------------- -// Routines to display message boxes on screen. Do our own, instead of using -// MessageBox, because that is not consistent from version to version and -// there's word wrap problems. -//----------------------------------------------------------------------------- - -HWND MessageWnd, OkButton; -bool MessageDone; -int MessageWidth, MessageHeight; -const char *MessageString; - -static LRESULT CALLBACK MessageProc(HWND hwnd, UINT msg, WPARAM wParam, - LPARAM lParam) -{ - switch (msg) { - case WM_COMMAND: - if((HWND)lParam == OkButton && wParam == BN_CLICKED) { - MessageDone = true; - } - break; - - case WM_CLOSE: - case WM_DESTROY: - MessageDone = true; - break; - - case WM_PAINT: { - PAINTSTRUCT ps; - HDC hdc = BeginPaint(hwnd, &ps); - SelectObject(hdc, FixedFont); - SetTextColor(hdc, 0x000000); - SetBkMode(hdc, TRANSPARENT); - RECT rc; - SetRect(&rc, 10, 10, MessageWidth, MessageHeight); - std::wstring text = Widen(MessageString); - DrawText(hdc, text.c_str(), text.length(), &rc, DT_LEFT | DT_WORDBREAK); - EndPaint(hwnd, &ps); - break; - } - - default: - return DefWindowProc(hwnd, msg, wParam, lParam); - } - - return 1; -} - -HWND CreateWindowClient(DWORD exStyle, const wchar_t *className, const wchar_t *windowName, - DWORD style, int x, int y, int width, int height, HWND parent, - HMENU menu, HINSTANCE instance, void *param) -{ - HWND h = CreateWindowExW(exStyle, className, windowName, style, x, y, - width, height, parent, menu, instance, param); - - RECT r; - GetClientRect(h, &r); - width = width - (r.right - width); - height = height - (r.bottom - height); - - SetWindowPos(h, HWND_TOP, x, y, width, height, 0); - - return h; -} - -void SolveSpace::DoMessageBox(const char *str, int rows, int cols, bool error) -{ - EnableWindow(GraphicsWnd, false); - EnableWindow(TextWnd, false); - - // Register the window class for our dialog. - WNDCLASSEX wc = {}; - wc.cbSize = sizeof(wc); - wc.style = CS_BYTEALIGNCLIENT | CS_BYTEALIGNWINDOW | CS_OWNDC; - wc.lpfnWndProc = (WNDPROC)MessageProc; - wc.hInstance = Instance; - wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW; - wc.lpszClassName = L"MessageWnd"; - wc.lpszMenuName = NULL; - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hIcon = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000), - IMAGE_ICON, 32, 32, 0); - wc.hIconSm = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000), - IMAGE_ICON, 16, 16, 0); - RegisterClassEx(&wc); - - // Create the window. - MessageString = str; - RECT r; - GetWindowRect(GraphicsWnd, &r); - const char *title = error ? "SolveSpace - Error" : "SolveSpace - Message"; - int width = cols*SS.TW.CHAR_WIDTH + 20, - height = rows*SS.TW.LINE_HEIGHT + 60; - MessageWidth = width; - MessageHeight = height; - MessageWnd = CreateWindowClient(0, L"MessageWnd", Widen(title).c_str(), - WS_OVERLAPPED | WS_SYSMENU, - r.left + 100, r.top + 100, width, height, NULL, NULL, Instance, NULL); - - OkButton = CreateWindowExW(0, WC_BUTTON, L"OK", - WS_CHILD | WS_TABSTOP | WS_CLIPSIBLINGS | WS_VISIBLE | BS_DEFPUSHBUTTON, - (width - 70)/2, rows*SS.TW.LINE_HEIGHT + 20, - 70, 25, MessageWnd, NULL, Instance, NULL); - SendMessage(OkButton, WM_SETFONT, (WPARAM)FixedFont, true); - - ShowWindow(MessageWnd, true); - SetFocus(OkButton); - - MSG msg; - DWORD ret; - MessageDone = false; - while((ret = GetMessage(&msg, NULL, 0, 0)) != 0 && !MessageDone) { - if((msg.message == WM_KEYDOWN && - (msg.wParam == VK_RETURN || - msg.wParam == VK_ESCAPE)) || - (msg.message == WM_KEYUP && - (msg.wParam == VK_SPACE))) - { - MessageDone = true; - break; - } - - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - MessageString = NULL; - EnableWindow(TextWnd, true); - EnableWindow(GraphicsWnd, true); - SetForegroundWindow(GraphicsWnd); - DestroyWindow(MessageWnd); -} - -void SolveSpace::AddContextMenuItem(const char *label, int id) -{ - if(!ContextMenu) ContextMenu = CreatePopupMenu(); - - if(id == CONTEXT_SUBMENU) { - AppendMenuW(ContextMenu, MF_STRING | MF_POPUP, - (UINT_PTR)ContextSubmenu, Widen(label).c_str()); - ContextSubmenu = NULL; - } else { - HMENU m = ContextSubmenu ? ContextSubmenu : ContextMenu; - if(id == CONTEXT_SEPARATOR) { - AppendMenuW(m, MF_SEPARATOR, 0, L""); - } else { - AppendMenuW(m, MF_STRING, id, Widen(label).c_str()); - } - } -} - -void SolveSpace::CreateContextSubmenu(void) -{ - ContextSubmenu = CreatePopupMenu(); -} - -int SolveSpace::ShowContextMenu(void) -{ - POINT p; - GetCursorPos(&p); - int r = TrackPopupMenu(ContextMenu, - TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_TOPALIGN, - p.x, p.y, 0, GraphicsWnd, NULL); - - DestroyMenu(ContextMenu); - ContextMenu = NULL; - return r; -} - -void CALLBACK TimerCallback(HWND hwnd, UINT msg, UINT_PTR id, DWORD time) -{ - // The timer is periodic, so needs to be killed explicitly. - KillTimer(GraphicsWnd, 1); - SS.GW.TimerCallback(); - SS.TW.TimerCallback(); -} -void SolveSpace::SetTimerFor(int milliseconds) -{ - SetTimer(GraphicsWnd, 1, milliseconds, TimerCallback); -} - -void SolveSpace::ScheduleLater() -{ -} - -static void CALLBACK AutosaveCallback(HWND hwnd, UINT msg, UINT_PTR id, DWORD time) -{ - KillTimer(GraphicsWnd, 1); - SS.Autosave(); -} - -void SolveSpace::SetAutosaveTimerFor(int minutes) -{ - SetTimer(GraphicsWnd, 2, minutes * 60 * 1000, AutosaveCallback); -} - -static void GetWindowSize(HWND hwnd, int *w, int *h) -{ - RECT r; - GetClientRect(hwnd, &r); - *w = r.right - r.left; - *h = r.bottom - r.top; -} -void SolveSpace::GetGraphicsWindowSize(int *w, int *h) -{ - GetWindowSize(GraphicsWnd, w, h); -} -void SolveSpace::GetTextWindowSize(int *w, int *h) -{ - GetWindowSize(TextWnd, w, h); -} - -void SolveSpace::OpenWebsite(const char *url) { - ShellExecuteW(GraphicsWnd, L"open", Widen(url).c_str(), NULL, NULL, SW_SHOWNORMAL); -} - -void SolveSpace::ExitNow(void) { - PostQuitMessage(0); -} - -//----------------------------------------------------------------------------- -// Helpers so that we can read/write registry keys from the platform- -// independent code. -//----------------------------------------------------------------------------- -inline int CLAMP(int v, int a, int b) { - // Clamp it to the range [a, b] - if(v <= a) return a; - if(v >= b) return b; - return v; -} - -static HKEY GetRegistryKey() -{ - HKEY Software; - if(RegOpenKeyExW(HKEY_CURRENT_USER, L"Software", 0, - KEY_ALL_ACCESS, &Software) != ERROR_SUCCESS) - return NULL; - - HKEY SolveSpace; - if(RegCreateKeyExW(Software, L"SolveSpace", 0, NULL, 0, - KEY_ALL_ACCESS, NULL, &SolveSpace, NULL) != ERROR_SUCCESS) - return NULL; - - RegCloseKey(Software); - - return SolveSpace; -} - -void SolveSpace::CnfFreezeInt(uint32_t val, const std::string &name) -{ - HKEY SolveSpace = GetRegistryKey(); - RegSetValueExW(SolveSpace, &Widen(name)[0], 0, - REG_DWORD, (const BYTE*) &val, sizeof(DWORD)); - RegCloseKey(SolveSpace); -} -void SolveSpace::CnfFreezeFloat(float val, const std::string &name) -{ - static_assert(sizeof(float) == sizeof(DWORD), - "sizes of float and DWORD must match"); - HKEY SolveSpace = GetRegistryKey(); - RegSetValueExW(SolveSpace, &Widen(name)[0], 0, - REG_DWORD, (const BYTE*) &val, sizeof(DWORD)); - RegCloseKey(SolveSpace); -} -void SolveSpace::CnfFreezeString(const std::string &str, const std::string &name) -{ - HKEY SolveSpace = GetRegistryKey(); - std::wstring strW = Widen(str); - RegSetValueExW(SolveSpace, &Widen(name)[0], 0, - REG_SZ, (const BYTE*) &strW[0], (strW.length() + 1) * 2); - RegCloseKey(SolveSpace); -} -static void FreezeWindowPos(HWND hwnd, const std::string &name) -{ - RECT r; - GetWindowRect(hwnd, &r); - CnfFreezeInt(r.left, name + "_left"); - CnfFreezeInt(r.right, name + "_right"); - CnfFreezeInt(r.top, name + "_top"); - CnfFreezeInt(r.bottom, name + "_bottom"); - - CnfFreezeInt(IsZoomed(hwnd), name + "_maximized"); -} - -uint32_t SolveSpace::CnfThawInt(uint32_t val, const std::string &name) -{ - HKEY SolveSpace = GetRegistryKey(); - DWORD type, newval, len = sizeof(DWORD); - LONG result = RegQueryValueEx(SolveSpace, &Widen(name)[0], NULL, - &type, (BYTE*) &newval, &len); - RegCloseKey(SolveSpace); - - if(result == ERROR_SUCCESS && type == REG_DWORD) - return newval; - else - return val; -} -float SolveSpace::CnfThawFloat(float val, const std::string &name) -{ - HKEY SolveSpace = GetRegistryKey(); - DWORD type, len = sizeof(DWORD); - float newval; - LONG result = RegQueryValueExW(SolveSpace, &Widen(name)[0], NULL, - &type, (BYTE*) &newval, &len); - RegCloseKey(SolveSpace); - - if(result == ERROR_SUCCESS && type == REG_DWORD) - return newval; - else - return val; -} -std::string SolveSpace::CnfThawString(const std::string &val, const std::string &name) -{ - HKEY SolveSpace = GetRegistryKey(); - DWORD type, len; - if(RegQueryValueExW(SolveSpace, &Widen(name)[0], NULL, - &type, NULL, &len) != ERROR_SUCCESS || type != REG_SZ) { - RegCloseKey(SolveSpace); - return val; - } - - std::wstring newval; - newval.resize(len / 2 - 1); - if(RegQueryValueExW(SolveSpace, &Widen(name)[0], NULL, - NULL, (BYTE*) &newval[0], &len) != ERROR_SUCCESS) { - RegCloseKey(SolveSpace); - return val; - } - - RegCloseKey(SolveSpace); - return Narrow(newval); -} -static void ThawWindowPos(HWND hwnd, const std::string &name) -{ - RECT r; - GetWindowRect(hwnd, &r); - r.left = CnfThawInt(r.left, name + "_left"); - r.right = CnfThawInt(r.right, name + "_right"); - r.top = CnfThawInt(r.top, name + "_top"); - r.bottom = CnfThawInt(r.bottom, name + "_bottom"); - - HMONITOR hMonitor = MonitorFromRect(&r, MONITOR_DEFAULTTONEAREST);; - MONITORINFO mi; - mi.cbSize = sizeof(mi); - GetMonitorInfo(hMonitor, &mi); - - // If it somehow ended up off-screen, then put it back. - RECT dr = mi.rcMonitor; - r.left = CLAMP(r.left, dr.left, dr.right); - r.right = CLAMP(r.right, dr.left, dr.right); - r.top = CLAMP(r.top, dr.top, dr.bottom); - r.bottom = CLAMP(r.bottom, dr.top, dr.bottom); - MoveWindow(hwnd, r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE); - - if(CnfThawInt(FALSE, name + "_maximized")) - ShowWindow(hwnd, SW_MAXIMIZE); -} - -void SolveSpace::SetCurrentFilename(const std::string &filename) { - if(!filename.empty()) { - SetWindowTextW(GraphicsWnd, Widen("SolveSpace - " + filename).c_str()); - } else { - SetWindowTextW(GraphicsWnd, L"SolveSpace - (not yet saved)"); - } -} - -void SolveSpace::SetMousePointerToHand(bool yes) { - SetCursor(LoadCursor(NULL, yes ? IDC_HAND : IDC_ARROW)); -} - -static void PaintTextWnd(HDC hdc) -{ - wglMakeCurrent(GetDC(TextWnd), TextGl); - - SS.TW.Paint(); - SwapBuffers(GetDC(TextWnd)); - - // Leave the graphics window context active, except when we're painting - // this text window. - wglMakeCurrent(GetDC(GraphicsWnd), GraphicsGl); -} - -void SolveSpace::MoveTextScrollbarTo(int pos, int maxPos, int page) -{ - SCROLLINFO si = {}; - si.cbSize = sizeof(si); - si.fMask = SIF_DISABLENOSCROLL | SIF_ALL; - si.nMin = 0; - si.nMax = maxPos; - si.nPos = pos; - si.nPage = page; - SetScrollInfo(TextWndScrollBar, SB_CTL, &si, true); -} - -void HandleTextWindowScrollBar(WPARAM wParam, LPARAM lParam) -{ - int maxPos, minPos, pos; - GetScrollRange(TextWndScrollBar, SB_CTL, &minPos, &maxPos); - pos = GetScrollPos(TextWndScrollBar, SB_CTL); - - switch(LOWORD(wParam)) { - case SB_LINEUP: pos--; break; - case SB_PAGEUP: pos -= 4; break; - - case SB_LINEDOWN: pos++; break; - case SB_PAGEDOWN: pos += 4; break; - - case SB_TOP: pos = 0; break; - - case SB_BOTTOM: pos = maxPos; break; - - case SB_THUMBTRACK: - case SB_THUMBPOSITION: pos = HIWORD(wParam); break; - } - - SS.TW.ScrollbarEvent(pos); -} - -static void MouseWheel(int thisDelta) { - static int DeltaAccum; - int delta = 0; - // Handle mouse deltas of less than 120 (like from an un-detented mouse - // wheel) correctly, even though no one ever uses those. - DeltaAccum += thisDelta; - while(DeltaAccum >= 120) { - DeltaAccum -= 120; - delta += 120; - } - while(DeltaAccum <= -120) { - DeltaAccum += 120; - delta -= 120; - } - if(delta == 0) return; - - POINT pt; - GetCursorPos(&pt); - HWND hw = WindowFromPoint(pt); - - // Make the mousewheel work according to which window the mouse is - // over, not according to which window is active. - bool inTextWindow; - if(hw == TextWnd) { - inTextWindow = true; - } else if(hw == GraphicsWnd) { - inTextWindow = false; - } else if(GetForegroundWindow() == TextWnd) { - inTextWindow = true; - } else { - inTextWindow = false; - } - - if(inTextWindow) { - int i; - for(i = 0; i < abs(delta/40); i++) { - HandleTextWindowScrollBar(delta > 0 ? SB_LINEUP : SB_LINEDOWN, 0); - } - } else { - SS.GW.MouseScroll(LastMousePos.x, LastMousePos.y, delta); - } -} - -LRESULT CALLBACK TextWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_ERASEBKGND: - break; - - case WM_CLOSE: - case WM_DESTROY: - SolveSpaceUI::MenuFile(GraphicsWindow::MNU_EXIT); - break; - - case WM_PAINT: { - // Actually paint the text window, with gl. - PaintTextWnd(GetDC(TextWnd)); - // And then just make Windows happy. - PAINTSTRUCT ps; - HDC hdc = BeginPaint(hwnd, &ps); - EndPaint(hwnd, &ps); - break; - } - - case WM_SIZING: { - RECT *r = (RECT *)lParam; - int hc = (r->bottom - r->top) - ClientIsSmallerBy; - int extra = hc % (SS.TW.LINE_HEIGHT/2); - switch(wParam) { - case WMSZ_BOTTOM: - case WMSZ_BOTTOMLEFT: - case WMSZ_BOTTOMRIGHT: - r->bottom -= extra; - break; - - case WMSZ_TOP: - case WMSZ_TOPLEFT: - case WMSZ_TOPRIGHT: - r->top += extra; - break; - } - int tooNarrow = (SS.TW.MIN_COLS*SS.TW.CHAR_WIDTH) - - (r->right - r->left); - if(tooNarrow >= 0) { - switch(wParam) { - case WMSZ_RIGHT: - case WMSZ_BOTTOMRIGHT: - case WMSZ_TOPRIGHT: - r->right += tooNarrow; - break; - - case WMSZ_LEFT: - case WMSZ_BOTTOMLEFT: - case WMSZ_TOPLEFT: - r->left -= tooNarrow; - break; - } - } - break; - } - - case WM_MOUSELEAVE: - SS.TW.MouseLeave(); - break; - - case WM_LBUTTONDOWN: - case WM_MOUSEMOVE: { - // We need this in order to get the WM_MOUSELEAVE - TRACKMOUSEEVENT tme = {}; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = TextWnd; - TrackMouseEvent(&tme); - - // And process the actual message - int x = LOWORD(lParam); - int y = HIWORD(lParam); - SS.TW.MouseEvent(msg == WM_LBUTTONDOWN, wParam & MK_LBUTTON, x, y); - break; - } - - case WM_SIZE: { - RECT r; - GetWindowRect(TextWndScrollBar, &r); - int sw = r.right - r.left; - GetClientRect(hwnd, &r); - MoveWindow(TextWndScrollBar, r.right - sw, r.top, sw, - (r.bottom - r.top), true); - // If the window is growing, then the scrollbar position may - // be moving, so it's as if we're dragging the scrollbar. - HandleTextWindowScrollBar((WPARAM)-1, -1); - InvalidateRect(TextWnd, NULL, false); - break; - } - - case WM_MOUSEWHEEL: - MouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); - break; - - case WM_VSCROLL: - HandleTextWindowScrollBar(wParam, lParam); - break; - - default: - return DefWindowProc(hwnd, msg, wParam, lParam); - } - - return 1; -} - -static std::string EditControlText(HWND hwnd) -{ - std::wstring result; - result.resize(GetWindowTextLength(hwnd)); - GetWindowTextW(hwnd, &result[0], result.length() + 1); - return Narrow(result); -} - -static bool ProcessKeyDown(WPARAM wParam) -{ - if(GraphicsEditControlIsVisible() && wParam != VK_ESCAPE) { - if(wParam == VK_RETURN) { - SS.GW.EditControlDone(EditControlText(GraphicsEditControl).c_str()); - return true; - } else { - return false; - } - } - if(TextEditControlIsVisible() && wParam != VK_ESCAPE) { - if(wParam == VK_RETURN) { - SS.TW.EditControlDone(EditControlText(TextEditControl).c_str()); - } else { - return false; - } - } - - int c; - switch(wParam) { - case VK_OEM_PLUS: c = '+'; break; - case VK_OEM_MINUS: c = '-'; break; - case VK_ESCAPE: c = 27; break; - case VK_OEM_1: c = ';'; break; - case VK_OEM_3: c = '`'; break; - case VK_OEM_4: c = '['; break; - case VK_OEM_6: c = ']'; break; - case VK_OEM_5: c = '\\'; break; - case VK_OEM_PERIOD: c = '.'; break; - case VK_SPACE: c = ' '; break; - case VK_DELETE: c = 127; break; - case VK_TAB: c = '\t'; break; - - case VK_BROWSER_BACK: - case VK_BACK: c = '\b'; break; - - case VK_F1: - case VK_F2: - case VK_F3: - case VK_F4: - case VK_F5: - case VK_F6: - case VK_F7: - case VK_F8: - case VK_F9: - case VK_F10: - case VK_F11: - case VK_F12: c = ((int)wParam - VK_F1) + 0xf1; break; - - // These overlap with some character codes that I'm using, so - // don't let them trigger by accident. - case VK_F16: - case VK_INSERT: - case VK_EXECUTE: - case VK_APPS: - case VK_LWIN: - case VK_RWIN: return false; - - default: - c = (int)wParam; - break; - } - if(GetAsyncKeyState(VK_SHIFT) & 0x8000) c |= GraphicsWindow::SHIFT_MASK; - if(GetAsyncKeyState(VK_CONTROL) & 0x8000) c |= GraphicsWindow::CTRL_MASK; - - switch(c) { - case GraphicsWindow::SHIFT_MASK | '.': c = '>'; break; - } - - for(int i = 0; SS.GW.menu[i].level >= 0; i++) { - if(c == SS.GW.menu[i].accel) { - (SS.GW.menu[i].fn)((GraphicsWindow::MenuId)SS.GW.menu[i].id); - break; - } - } - - if(SS.GW.KeyDown(c)) return true; - - // No accelerator; process the key as normal. - return false; -} - -void SolveSpace::ToggleMenuBar(void) -{ - // Implement me -} -bool SolveSpace::MenuBarIsVisible(void) -{ - // Implement me - return true; -} - -void SolveSpace::ShowTextWindow(bool visible) -{ - ShowWindow(TextWnd, visible ? SW_SHOWNOACTIVATE : SW_HIDE); -} - -static void CreateGlContext(HWND hwnd, HGLRC *glrc) -{ - HDC hdc = GetDC(hwnd); - - PIXELFORMATDESCRIPTOR pfd = {}; - int pixelFormat; - - pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | - PFD_DOUBLEBUFFER; - pfd.dwLayerMask = PFD_MAIN_PLANE; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cColorBits = 32; - pfd.cDepthBits = 24; - pfd.cAccumBits = 0; - pfd.cStencilBits = 0; - - pixelFormat = ChoosePixelFormat(hdc, &pfd); - if(!pixelFormat) oops(); - - if(!SetPixelFormat(hdc, pixelFormat, &pfd)) oops(); - - *glrc = wglCreateContext(hdc); - wglMakeCurrent(hdc, *glrc); -} - -void SolveSpace::PaintGraphics(void) -{ - SS.GW.Paint(); - SwapBuffers(GetDC(GraphicsWnd)); -} -void SolveSpace::InvalidateGraphics(void) -{ - InvalidateRect(GraphicsWnd, NULL, false); -} - -void SolveSpace::ToggleFullScreen(void) -{ - // Implement me -} -bool SolveSpace::FullScreenIsActive(void) -{ - // Implement me - return false; -} - -int64_t SolveSpace::GetMilliseconds(void) -{ - LARGE_INTEGER t, f; - QueryPerformanceCounter(&t); - QueryPerformanceFrequency(&f); - LONGLONG d = t.QuadPart/(f.QuadPart/1000); - return (int64_t)d; -} - -int64_t SolveSpace::GetUnixTime(void) -{ -#ifdef __MINGW32__ - time_t ret; - time(&ret); -#else - __time64_t ret; - _time64(&ret); -#endif - return (int64_t)ret; -} - -void SolveSpace::InvalidateText(void) -{ - InvalidateRect(TextWnd, NULL, false); -} - -static void ShowEditControl(HWND h, int x, int y, int fontHeight, int minWidthChars, - bool isMonospace, const std::wstring &s) { - static HFONT hf; - if(hf) DeleteObject(hf); - hf = CreateFontW(-fontHeight, 0, 0, 0, - FW_REGULAR, false, false, false, ANSI_CHARSET, - OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, - DEFAULT_QUALITY, FF_DONTCARE, isMonospace ? L"Lucida Console" : L"Arial"); - if(hf) SendMessage(h, WM_SETFONT, (WPARAM)hf, false); - else SendMessage(h, WM_SETFONT, (WPARAM)(HFONT)GetStockObject(SYSTEM_FONT), false); - SendMessage(h, EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN, 0); - - HDC hdc = GetDC(h); - TEXTMETRICW tm; - SIZE ts; - SelectObject(hdc, hf); - GetTextMetrics(hdc, &tm); - GetTextExtentPoint32W(hdc, s.c_str(), s.length(), &ts); - ReleaseDC(h, hdc); - - RECT rc; - rc.left = x; - rc.top = y - tm.tmAscent; - // Add one extra char width to avoid scrolling. - rc.right = x + std::max(tm.tmAveCharWidth * minWidthChars, - ts.cx + tm.tmAveCharWidth); - rc.bottom = y + tm.tmDescent; - - AdjustWindowRectEx(&rc, 0, false, WS_EX_CLIENTEDGE); - MoveWindow(h, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, true); - ShowWindow(h, SW_SHOW); - if(!s.empty()) { - SendMessage(h, WM_SETTEXT, 0, (LPARAM)s.c_str()); - SendMessage(h, EM_SETSEL, 0, s.length()); - SetFocus(h); - } -} -void SolveSpace::ShowTextEditControl(int x, int y, const std::string &str) -{ - if(GraphicsEditControlIsVisible()) return; - - ShowEditControl(TextEditControl, x, y, TextWindow::CHAR_HEIGHT, 30, - /*isMonospace=*/true, Widen(str)); -} -void SolveSpace::HideTextEditControl(void) -{ - ShowWindow(TextEditControl, SW_HIDE); -} -bool SolveSpace::TextEditControlIsVisible(void) -{ - return IsWindowVisible(TextEditControl) ? true : false; -} -void SolveSpace::ShowGraphicsEditControl(int x, int y, int fontHeight, int minWidthChars, - const std::string &str) -{ - if(GraphicsEditControlIsVisible()) return; - - RECT r; - GetClientRect(GraphicsWnd, &r); - x = x + (r.right - r.left)/2; - y = (r.bottom - r.top)/2 - y; - - ShowEditControl(GraphicsEditControl, x, y, fontHeight, minWidthChars, - /*isMonospace=*/false, Widen(str)); -} -void SolveSpace::HideGraphicsEditControl(void) -{ - ShowWindow(GraphicsEditControl, SW_HIDE); -} -bool SolveSpace::GraphicsEditControlIsVisible(void) -{ - return IsWindowVisible(GraphicsEditControl) ? true : false; -} - -LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam, - LPARAM lParam) -{ - switch (msg) { - case WM_ERASEBKGND: - break; - - case WM_SIZE: - InvalidateRect(GraphicsWnd, NULL, false); - break; - - case WM_PAINT: { - // Actually paint the window, with gl. - PaintGraphics(); - // And make Windows happy. - PAINTSTRUCT ps; - HDC hdc = BeginPaint(hwnd, &ps); - EndPaint(hwnd, &ps); - break; - } - - case WM_MOUSELEAVE: - SS.GW.MouseLeave(); - break; - - case WM_MOUSEMOVE: - case WM_LBUTTONDOWN: - case WM_LBUTTONUP: - case WM_LBUTTONDBLCLK: - case WM_RBUTTONDOWN: - case WM_RBUTTONUP: - case WM_MBUTTONDOWN: { - int x = LOWORD(lParam); - int y = HIWORD(lParam); - - // We need this in order to get the WM_MOUSELEAVE - TRACKMOUSEEVENT tme = {}; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = GraphicsWnd; - TrackMouseEvent(&tme); - - // Convert to xy (vs. ij) style coordinates, with (0, 0) at center - RECT r; - GetClientRect(GraphicsWnd, &r); - x = x - (r.right - r.left)/2; - y = (r.bottom - r.top)/2 - y; - - LastMousePos.x = x; - LastMousePos.y = y; - - if(msg == WM_LBUTTONDOWN) { - SS.GW.MouseLeftDown(x, y); - } else if(msg == WM_LBUTTONUP) { - SS.GW.MouseLeftUp(x, y); - } else if(msg == WM_LBUTTONDBLCLK) { - SS.GW.MouseLeftDoubleClick(x, y); - } else if(msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) { - SS.GW.MouseMiddleOrRightDown(x, y); - } else if(msg == WM_RBUTTONUP) { - SS.GW.MouseRightUp(x, y); - } else if(msg == WM_MOUSEMOVE) { - SS.GW.MouseMoved(x, y, - !!(wParam & MK_LBUTTON), - !!(wParam & MK_MBUTTON), - !!(wParam & MK_RBUTTON), - !!(wParam & MK_SHIFT), - !!(wParam & MK_CONTROL)); - } else { - oops(); - } - break; - } - case WM_MOUSEWHEEL: - MouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); - break; - - case WM_COMMAND: { - if(HIWORD(wParam) == 0) { - int id = LOWORD(wParam); - if((id >= RECENT_OPEN && id < (RECENT_OPEN + MAX_RECENT))) { - SolveSpaceUI::MenuFile(id); - break; - } - if((id >= RECENT_LINK && id < (RECENT_LINK + MAX_RECENT))) { - Group::MenuGroup(id); - break; - } - int i; - for(i = 0; SS.GW.menu[i].level >= 0; i++) { - if(id == SS.GW.menu[i].id) { - (SS.GW.menu[i].fn)((GraphicsWindow::MenuId)id); - break; - } - } - if(SS.GW.menu[i].level < 0) oops(); - } - break; - } - - case WM_CLOSE: - case WM_DESTROY: - SolveSpaceUI::MenuFile(GraphicsWindow::MNU_EXIT); - return 1; - - default: - return DefWindowProc(hwnd, msg, wParam, lParam); - } - - return 1; -} - -//----------------------------------------------------------------------------- -// Common dialog routines, to open or save a file. -//----------------------------------------------------------------------------- -static std::string ConvertFilters(const FileFilter ssFilters[]) { - std::string filter; - for(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) { - std::string desc, patterns; - for(const char *const *ssPattern = ssFilter->patterns; *ssPattern; ssPattern++) { - std::string pattern = "*." + std::string(*ssPattern); - if(desc == "") - desc = pattern; - else - desc += ", " + pattern; - if(patterns == "") - patterns = pattern; - else - patterns += ";" + pattern; - } - filter += std::string(ssFilter->name) + " (" + desc + ")" + '\0'; - filter += patterns + '\0'; - } - filter += '\0'; - return filter; -} - -static bool OpenSaveFile(bool isOpen, std::string *filename, const std::string &defExtension, - const FileFilter filters[]) { - // UNC paths may be as long as 32767 characters. - // Unfortunately, the Get*FileName API does not provide any way to use it - // except with a preallocated buffer of fixed size, so we use something - // reasonably large. - const int len = 32768; - wchar_t filenameC[len] = {}; - wcsncpy(filenameC, Widen(*filename).c_str(), len - 1); - - std::wstring selPatternW = Widen(ConvertFilters(filters)); - std::wstring defExtensionW = Widen(defExtension); - - OPENFILENAME ofn = {}; - ofn.lStructSize = sizeof(ofn); - ofn.hInstance = Instance; - ofn.hwndOwner = GraphicsWnd; - ofn.lpstrFilter = selPatternW.c_str(); - ofn.lpstrDefExt = defExtensionW.c_str(); - ofn.lpstrFile = filenameC; - ofn.nMaxFile = len; - ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; - - EnableWindow(GraphicsWnd, false); - EnableWindow(TextWnd, false); - - BOOL r; - if(isOpen) { - r = GetOpenFileNameW(&ofn); - } else { - r = GetSaveFileNameW(&ofn); - } - - EnableWindow(TextWnd, true); - EnableWindow(GraphicsWnd, true); - SetForegroundWindow(GraphicsWnd); - - if(r) *filename = Narrow(filenameC); - return r ? true : false; -} - -bool SolveSpace::GetOpenFile(std::string *filename, const std::string &defExtension, - const FileFilter filters[]) -{ - return OpenSaveFile(true, filename, defExtension, filters); -} - -bool SolveSpace::GetSaveFile(std::string *filename, const std::string &defExtension, - const FileFilter filters[]) -{ - return OpenSaveFile(false, filename, defExtension, filters); -} - -DialogChoice SolveSpace::SaveFileYesNoCancel(void) -{ - EnableWindow(GraphicsWnd, false); - EnableWindow(TextWnd, false); - - int r = MessageBoxW(GraphicsWnd, - L"The file has changed since it was last saved.\n\n" - L"Do you want to save the changes?", L"SolveSpace", - MB_YESNOCANCEL | MB_ICONWARNING); - - EnableWindow(TextWnd, true); - EnableWindow(GraphicsWnd, true); - SetForegroundWindow(GraphicsWnd); - - switch(r) { - case IDYES: - return DIALOG_YES; - case IDNO: - return DIALOG_NO; - case IDCANCEL: - default: - return DIALOG_CANCEL; - } -} - -DialogChoice SolveSpace::LoadAutosaveYesNo(void) -{ - EnableWindow(GraphicsWnd, false); - EnableWindow(TextWnd, false); - - int r = MessageBoxW(GraphicsWnd, - L"An autosave file is availible for this project.\n\n" - L"Do you want to load the autosave file instead?", L"SolveSpace", - MB_YESNO | MB_ICONWARNING); - - EnableWindow(TextWnd, true); - EnableWindow(GraphicsWnd, true); - SetForegroundWindow(GraphicsWnd); - - switch (r) { - case IDYES: - return DIALOG_YES; - case IDNO: - default: - return DIALOG_NO; - } -} - -DialogChoice SolveSpace::LocateImportedFileYesNoCancel(const std::string &filename, - bool canCancel) { - EnableWindow(GraphicsWnd, false); - EnableWindow(TextWnd, false); - - std::string message = - "The linked file " + filename + " is not present.\n\n" - "Do you want to locate it manually?\n\n" - "If you select \"No\", any geometry that depends on " - "the missing file will be removed."; - - int r = MessageBoxW(GraphicsWnd, Widen(message).c_str(), L"SolveSpace", - (canCancel ? MB_YESNOCANCEL : MB_YESNO) | MB_ICONWARNING); - - EnableWindow(TextWnd, true); - EnableWindow(GraphicsWnd, true); - SetForegroundWindow(GraphicsWnd); - - switch(r) { - case IDYES: - return DIALOG_YES; - case IDNO: - return DIALOG_NO; - case IDCANCEL: - default: - return DIALOG_CANCEL; - } -} - -std::vector SolveSpace::GetFontFiles() { - std::vector fonts; - - std::wstring fontsDir(MAX_PATH, '\0'); - fontsDir.resize(GetWindowsDirectoryW(&fontsDir[0], fontsDir.length())); - fontsDir += L"\\fonts\\"; - - WIN32_FIND_DATA wfd; - HANDLE h = FindFirstFileW((fontsDir + L"*").c_str(), &wfd); - while(h != INVALID_HANDLE_VALUE) { - fonts.push_back(Narrow(fontsDir) + Narrow(wfd.cFileName)); - if(!FindNextFileW(h, &wfd)) break; - } - - return fonts; -} - -static void MenuById(int id, bool yes, bool check) -{ - int i; - int subMenu = -1; - - for(i = 0; SS.GW.menu[i].level >= 0; i++) { - if(SS.GW.menu[i].level == 0) subMenu++; - - if(SS.GW.menu[i].id == id) { - if(subMenu < 0) oops(); - if(subMenu >= (int)arraylen(SubMenus)) oops(); - - if(check) { - CheckMenuItem(SubMenus[subMenu], id, - yes ? MF_CHECKED : MF_UNCHECKED); - } else { - EnableMenuItem(SubMenus[subMenu], id, - yes ? MF_ENABLED : MF_GRAYED); - } - return; - } - } - oops(); -} -void SolveSpace::CheckMenuById(int id, bool checked) -{ - MenuById(id, checked, true); -} -void SolveSpace::RadioMenuById(int id, bool selected) -{ - // Windows does not natively support radio-button menu items - MenuById(id, selected, true); -} -void SolveSpace::EnableMenuById(int id, bool enabled) -{ - MenuById(id, enabled, false); -} -static void DoRecent(HMENU m, int base) -{ - while(DeleteMenu(m, 0, MF_BYPOSITION)) - ; - int i, c = 0; - for(i = 0; i < MAX_RECENT; i++) { - if(!RecentFile[i].empty()) { - AppendMenuW(m, MF_STRING, base + i, Widen(RecentFile[i]).c_str()); - c++; - } - } - if(c == 0) AppendMenuW(m, MF_STRING | MF_GRAYED, 0, L"(no recent files)"); -} -void SolveSpace::RefreshRecentMenus(void) -{ - DoRecent(RecentOpenMenu, RECENT_OPEN); - DoRecent(RecentImportMenu, RECENT_LINK); -} - -HMENU CreateGraphicsWindowMenus(void) -{ - HMENU top = CreateMenu(); - HMENU m = 0; - - int i; - int subMenu = 0; - - for(i = 0; SS.GW.menu[i].level >= 0; i++) { - std::string label; - if(SS.GW.menu[i].label) { - std::string accel = MakeAcceleratorLabel(SS.GW.menu[i].accel); - const char *sep = accel.empty() ? "" : "\t"; - label = ssprintf("%s%s%s", SS.GW.menu[i].label, sep, accel.c_str()); - } - - if(SS.GW.menu[i].level == 0) { - m = CreateMenu(); - AppendMenuW(top, MF_STRING | MF_POPUP, (UINT_PTR)m, Widen(label).c_str()); - if(subMenu >= (int)arraylen(SubMenus)) oops(); - SubMenus[subMenu] = m; - subMenu++; - } else if(SS.GW.menu[i].level == 1) { - if(SS.GW.menu[i].id == GraphicsWindow::MNU_OPEN_RECENT) { - RecentOpenMenu = CreateMenu(); - AppendMenuW(m, MF_STRING | MF_POPUP, - (UINT_PTR)RecentOpenMenu, Widen(label).c_str()); - } else if(SS.GW.menu[i].id == GraphicsWindow::MNU_GROUP_RECENT) { - RecentImportMenu = CreateMenu(); - AppendMenuW(m, MF_STRING | MF_POPUP, - (UINT_PTR)RecentImportMenu, Widen(label).c_str()); - } else if(SS.GW.menu[i].label) { - AppendMenuW(m, MF_STRING, SS.GW.menu[i].id, Widen(label).c_str()); - } else { - AppendMenuW(m, MF_SEPARATOR, SS.GW.menu[i].id, L""); - } - } else oops(); - } - RefreshRecentMenus(); - - return top; -} - -static void CreateMainWindows(void) -{ - WNDCLASSEX wc = {}; - - wc.cbSize = sizeof(wc); - - // The graphics window, where the sketch is drawn and shown. - wc.style = CS_BYTEALIGNCLIENT | CS_BYTEALIGNWINDOW | CS_OWNDC | - CS_DBLCLKS; - wc.lpfnWndProc = (WNDPROC)GraphicsWndProc; - wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); - wc.lpszClassName = L"GraphicsWnd"; - wc.lpszMenuName = NULL; - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hIcon = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000), - IMAGE_ICON, 32, 32, 0); - wc.hIconSm = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000), - IMAGE_ICON, 16, 16, 0); - if(!RegisterClassEx(&wc)) oops(); - - HMENU top = CreateGraphicsWindowMenus(); - GraphicsWnd = CreateWindowExW(0, L"GraphicsWnd", - L"SolveSpace (not yet saved)", - WS_OVERLAPPED | WS_THICKFRAME | WS_CLIPCHILDREN | WS_MAXIMIZEBOX | - WS_MINIMIZEBOX | WS_SYSMENU | WS_SIZEBOX | WS_CLIPSIBLINGS, - 50, 50, 900, 600, NULL, top, Instance, NULL); - if(!GraphicsWnd) oops(); - - GraphicsEditControl = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDIT, L"", - WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS, - 50, 50, 100, 21, GraphicsWnd, NULL, Instance, NULL); - - // The text window, with a comand line and some textual information - // about the sketch. - wc.style &= ~CS_DBLCLKS; - wc.lpfnWndProc = (WNDPROC)TextWndProc; - wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); - wc.lpszClassName = L"TextWnd"; - wc.hCursor = NULL; - if(!RegisterClassEx(&wc)) oops(); - - // We get the desired Alt+Tab behaviour by specifying that the text - // window is a child of the graphics window. - TextWnd = CreateWindowExW(0, - L"TextWnd", L"SolveSpace - Property Browser", WS_THICKFRAME | WS_CLIPCHILDREN, - 650, 500, 420, 300, GraphicsWnd, (HMENU)NULL, Instance, NULL); - if(!TextWnd) oops(); - - TextWndScrollBar = CreateWindowExW(0, WC_SCROLLBAR, L"", WS_CHILD | - SBS_VERT | SBS_LEFTALIGN | WS_VISIBLE | WS_CLIPSIBLINGS, - 200, 100, 100, 100, TextWnd, NULL, Instance, NULL); - // Force the scrollbar to get resized to the window, - TextWndProc(TextWnd, WM_SIZE, 0, 0); - - TextEditControl = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDIT, L"", - WS_CHILD | ES_AUTOHSCROLL | WS_TABSTOP | WS_CLIPSIBLINGS, - 50, 50, 100, 21, TextWnd, NULL, Instance, NULL); - - // Now that all our windows exist, set up gl contexts. - CreateGlContext(TextWnd, &TextGl); - CreateGlContext(GraphicsWnd, &GraphicsGl); - - RECT r, rc; - GetWindowRect(TextWnd, &r); - GetClientRect(TextWnd, &rc); - ClientIsSmallerBy = (r.bottom - r.top) - (rc.bottom - rc.top); -} - -#ifdef HAVE_SPACEWARE -//----------------------------------------------------------------------------- -// Test if a message comes from the SpaceNavigator device. If yes, dispatch -// it appropriately and return true. Otherwise, do nothing and return false. -//----------------------------------------------------------------------------- -static bool ProcessSpaceNavigatorMsg(MSG *msg) { - if(SpaceNavigator == SI_NO_HANDLE) return false; - - SiGetEventData sged; - SiSpwEvent sse; - - SiGetEventWinInit(&sged, msg->message, msg->wParam, msg->lParam); - int ret = SiGetEvent(SpaceNavigator, 0, &sged, &sse); - if(ret == SI_NOT_EVENT) return false; - // So the device is a SpaceNavigator event, or a SpaceNavigator error. - - if(ret == SI_IS_EVENT) { - if(sse.type == SI_MOTION_EVENT) { - // The Z axis translation and rotation are both - // backwards in the default mapping. - double tx = sse.u.spwData.mData[SI_TX]*1.0, - ty = sse.u.spwData.mData[SI_TY]*1.0, - tz = -sse.u.spwData.mData[SI_TZ]*1.0, - rx = sse.u.spwData.mData[SI_RX]*0.001, - ry = sse.u.spwData.mData[SI_RY]*0.001, - rz = -sse.u.spwData.mData[SI_RZ]*0.001; - SS.GW.SpaceNavigatorMoved(tx, ty, tz, rx, ry, rz, - !!(GetAsyncKeyState(VK_SHIFT) & 0x8000)); - } else if(sse.type == SI_BUTTON_EVENT) { - int button; - button = SiButtonReleased(&sse); - if(button == SI_APP_FIT_BUTTON) SS.GW.SpaceNavigatorButtonUp(); - } - } - return true; -} -#endif // HAVE_SPACEWARE - -//----------------------------------------------------------------------------- -// Entry point into the program. -//----------------------------------------------------------------------------- -int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, - LPSTR lpCmdLine, INT nCmdShow) -{ - Instance = hInstance; - - InitCommonControls(); - - // A monospaced font - FixedFont = CreateFontW(SS.TW.CHAR_HEIGHT, SS.TW.CHAR_WIDTH, 0, 0, - FW_REGULAR, false, - false, false, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, - DEFAULT_QUALITY, FF_DONTCARE, L"Lucida Console"); - if(!FixedFont) - FixedFont = (HFONT)GetStockObject(SYSTEM_FONT); - - // Create the root windows: one for control, with text, and one for - // the graphics - CreateMainWindows(); - - ThawWindowPos(TextWnd, "TextWnd"); - ThawWindowPos(GraphicsWnd, "GraphicsWnd"); - - ShowWindow(TextWnd, SW_SHOWNOACTIVATE); - ShowWindow(GraphicsWnd, SW_SHOW); - - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - SwapBuffers(GetDC(GraphicsWnd)); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - SwapBuffers(GetDC(GraphicsWnd)); - - // Create the heaps for all dynamic memory (AllocTemporary, MemAlloc) - InitHeaps(); - - // Pull out the Unicode command line. - int argc; - LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc); - - // A filename may have been specified on the command line; if so, then - // strip any quotation marks, and make it absolute. - std::wstring filenameRel, filename; - if(argc >= 2) { - filenameRel = argv[1]; - if(filenameRel[0] == L'\"' && filenameRel[filenameRel.length() - 1] == L'\"') { - filenameRel.erase(0, 1); - filenameRel.erase(filenameRel.length() - 1, 1); - } - - DWORD len = GetFullPathNameW(&filenameRel[0], 0, NULL, NULL); - filename.resize(len - 1); - GetFullPathNameW(&filenameRel[0], len, &filename[0], NULL); - } - -#ifdef HAVE_SPACEWARE - // Initialize the SpaceBall, if present. Test if the driver is running - // first, to avoid a long timeout if it's not. - HWND swdc = FindWindowW(L"SpaceWare Driver Class", NULL); - if(swdc != NULL) { - SiOpenData sod; - SiInitialize(); - SiOpenWinInit(&sod, GraphicsWnd); - SpaceNavigator = - SiOpen("GraphicsWnd", SI_ANY_DEVICE, SI_NO_MASK, SI_EVENT, &sod); - SiSetUiMode(SpaceNavigator, SI_UI_NO_CONTROLS); - } -#endif - - // Call in to the platform-independent code, and let them do their init - SS.Init(); - if(!filename.empty()) - SS.OpenFile(Narrow(filename)); - - // And now it's the message loop. All calls in to the rest of the code - // will be from the wndprocs. - MSG msg; - DWORD ret; - while((ret = GetMessage(&msg, NULL, 0, 0)) != 0) { -#ifdef HAVE_SPACEWARE - // Is it a message from the six degree of freedom input device? - if(ProcessSpaceNavigatorMsg(&msg)) goto done; -#endif - - // A message from the keyboard, which should be processed as a keyboard - // accelerator? - if(msg.message == WM_KEYDOWN) { - if(ProcessKeyDown(msg.wParam)) goto done; - } - if(msg.message == WM_SYSKEYDOWN && msg.hwnd == TextWnd) { - // If the user presses the Alt key when the text window has focus, - // then that should probably go to the graphics window instead. - SetForegroundWindow(GraphicsWnd); - } - - // None of the above; so just a normal message to process. - TranslateMessage(&msg); - DispatchMessage(&msg); -done: - SS.DoLater(); - } - -#ifdef HAVE_SPACEWARE - if(swdc != NULL) { - if(SpaceNavigator != SI_NO_HANDLE) SiClose(SpaceNavigator); - SiTerminate(); - } -#endif - - // Write everything back to the registry - FreezeWindowPos(TextWnd, "TextWnd"); - FreezeWindowPos(GraphicsWnd, "GraphicsWnd"); - - // Free the memory we've used; anything that remains is a leak. - SK.Clear(); - SS.Clear(); - - return 0; -} diff --git a/src/win32/w32util.cpp b/src/win32/w32util.cpp deleted file mode 100644 index cba9c78..0000000 --- a/src/win32/w32util.cpp +++ /dev/null @@ -1,138 +0,0 @@ -//----------------------------------------------------------------------------- -// Utility functions that depend on Win32. Notably, our memory allocation; -// we use two separate allocators, one for long-lived stuff and one for -// stuff that gets freed after every regeneration of the model, to save us -// the trouble of freeing the latter explicitly. -// -// Copyright 2008-2013 Jonathan Westhues. -//----------------------------------------------------------------------------- -#include "solvespace.h" - -namespace SolveSpace { -static HANDLE PermHeap, TempHeap; - -void dbp(const char *str, ...) -{ - va_list f; - static char buf[1024*50]; - va_start(f, str); - _vsnprintf(buf, sizeof(buf), str, f); - va_end(f); - - // The native version of OutputDebugString, unlike most others, - // is OutputDebugStringA. - OutputDebugStringA(buf); -} - -std::string Narrow(const wchar_t *in) -{ - std::string out; - DWORD len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL); - out.resize(len - 1); - if(!WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], len, NULL, NULL)) - oops(); - return out; -} - -std::string Narrow(const std::wstring &in) -{ - if(in == L"") return ""; - - std::string out; - out.resize(WideCharToMultiByte(CP_UTF8, 0, &in[0], in.length(), NULL, 0, NULL, NULL)); - if(!WideCharToMultiByte(CP_UTF8, 0, &in[0], in.length(), - &out[0], out.length(), NULL, NULL)) - oops(); - return out; -} - -std::wstring Widen(const char *in) -{ - std::wstring out; - DWORD len = MultiByteToWideChar(CP_UTF8, 0, in, -1, NULL, 0); - out.resize(len - 1); - if(!MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], len)) - oops(); - return out; -} - -std::wstring Widen(const std::string &in) -{ - if(in == "") return L""; - - std::wstring out; - out.resize(MultiByteToWideChar(CP_UTF8, 0, &in[0], in.length(), NULL, 0)); - if(!MultiByteToWideChar(CP_UTF8, 0, &in[0], in.length(), &out[0], out.length())) - oops(); - return out; -} - -static std::string MakeUNCFilename(const std::string &filename) -{ - // Prepend \\?\ UNC prefix unless already an UNC path. - // We never try to fopen paths that are not absolute or - // contain separators inappropriate for the platform; - // thus, it is always safe to prepend this prefix. - std::string uncFilename = filename; - if(uncFilename.substr(0, 2) != "\\\\") - uncFilename = "\\\\?\\" + uncFilename; - return uncFilename; -} - -FILE *ssfopen(const std::string &filename, const char *mode) -{ - if(filename.length() != strlen(filename.c_str())) oops(); - return _wfopen(Widen(MakeUNCFilename(filename)).c_str(), Widen(mode).c_str()); -} - -void ssremove(const std::string &filename) -{ - if(filename.length() != strlen(filename.c_str())) oops(); - _wremove(Widen(filename).c_str()); -} - -//----------------------------------------------------------------------------- -// A separate heap, on which we allocate expressions. Maybe a bit faster, -// since no fragmentation issues whatsoever, and it also makes it possible -// to be sloppy with our memory management, and just free everything at once -// at the end. -//----------------------------------------------------------------------------- -void *AllocTemporary(size_t n) -{ - void *v = HeapAlloc(TempHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, n); - if(!v) oops(); - return v; -} -void FreeTemporary(void *p) { - HeapFree(TempHeap, HEAP_NO_SERIALIZE, p); -} -void FreeAllTemporary(void) -{ - if(TempHeap) HeapDestroy(TempHeap); - TempHeap = HeapCreate(HEAP_NO_SERIALIZE, 1024*1024*20, 0); - // This is a good place to validate, because it gets called fairly - // often. - vl(); -} - -void *MemAlloc(size_t n) { - void *p = HeapAlloc(PermHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, n); - if(!p) oops(); - return p; -} -void MemFree(void *p) { - HeapFree(PermHeap, HEAP_NO_SERIALIZE, p); -} - -void vl(void) { - if(!HeapValidate(TempHeap, HEAP_NO_SERIALIZE, NULL)) oops(); - if(!HeapValidate(PermHeap, HEAP_NO_SERIALIZE, NULL)) oops(); -} - -void InitHeaps(void) { - // Create the heap used for long-lived stuff (that gets freed piecewise). - PermHeap = HeapCreate(HEAP_NO_SERIALIZE, 1024*1024*20, 0); - // Create the heap that we use to store Exprs and other temp stuff. - FreeAllTemporary(); -} -} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..db812bf --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,138 @@ +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}) + +foreach(pkg_config_lib CAIRO) + include_directories(${${pkg_config_lib}_INCLUDE_DIRS}) + link_directories(${${pkg_config_lib}_LIBRARY_DIRS}) +endforeach() + +if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows") + add_definitions(-DTEST_BUILD_ON_WINDOWS) +endif() + +# test suite + +set(testsuite_SOURCES + harness.cpp + analysis/contour_area/test.cpp + core/expr/test.cpp + core/locale/test.cpp + core/path/test.cpp + constraint/points_coincident/test.cpp + constraint/pt_pt_distance/test.cpp + constraint/pt_plane_distance/test.cpp + constraint/pt_line_distance/test.cpp + constraint/pt_face_distance/test.cpp + constraint/proj_pt_distance/test.cpp + constraint/pt_in_plane/test.cpp + constraint/pt_on_line/test.cpp + constraint/pt_on_face/test.cpp + constraint/equal_length_lines/test.cpp + constraint/length_ratio/test.cpp + constraint/eq_len_pt_line_d/test.cpp + constraint/eq_pt_ln_distances/test.cpp + constraint/equal_angle/test.cpp + constraint/equal_line_arc_len/test.cpp + constraint/length_difference/test.cpp + constraint/symmetric/test.cpp + constraint/symmetric_horiz/test.cpp + constraint/symmetric_vert/test.cpp + constraint/symmetric_line/test.cpp + constraint/at_midpoint/test.cpp + constraint/horizontal/test.cpp + constraint/vertical/test.cpp + constraint/diameter/test.cpp + constraint/pt_on_circle/test.cpp + constraint/same_orientation/test.cpp + constraint/angle/test.cpp + constraint/parallel/test.cpp + constraint/perpendicular/test.cpp + constraint/arc_line_tangent/test.cpp + constraint/cubic_line_tangent/test.cpp + constraint/curve_curve_tangent/test.cpp + constraint/equal_radius/test.cpp + constraint/where_dragged/test.cpp + constraint/comment/test.cpp + request/arc_of_circle/test.cpp + request/circle/test.cpp + request/cubic/test.cpp + request/cubic_periodic/test.cpp + request/datum_point/test.cpp + request/image/test.cpp + request/line_segment/test.cpp + request/ttf_text/test.cpp + request/workplane/test.cpp + group/link/test.cpp + group/translate_asy/test.cpp + group/translate_nd/test.cpp +) + +add_executable(solvespace-testsuite + ${testsuite_SOURCES} + $) + +target_link_libraries(solvespace-testsuite + solvespace-headless + ${COVERAGE_LIBRARY}) + +add_dependencies(solvespace-testsuite + resources) + +add_custom_target(test_solvespace + COMMAND $ + COMMENT "Testing SolveSpace" + VERBATIM) + +# coverage reports + +if(ENABLE_COVERAGE) + set(LCOV_FLAGS -q --gcov-tool ${GCOV}) + set(LCOV_FLAGS ${LCOV_FLAGS} --rc lcov_branch_coverage=1) + set(LCOV_FLAGS ${LCOV_FLAGS} --rc "lcov_excl_line=(ssassert|switch)") + set(LCOV_FLAGS ${LCOV_FLAGS} --rc "lcov_excl_br_line=BRANCH_ALWAYS_TAKEN") + set(LCOV_COLLECT -c -b ${CMAKE_SOURCE_DIR}/src -d ${CMAKE_BINARY_DIR}/src --no-external) + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/coverage_base.info + COMMAND ${LCOV} ${LCOV_FLAGS} ${LCOV_COLLECT} + -o ${CMAKE_BINARY_DIR}/coverage_base.info -i + DEPENDS solvespace-testsuite + COMMENT "Importing baseline coverage data" + VERBATIM) + + add_custom_target(coverage_solvespace ALL + COMMAND ${LCOV} ${LCOV_FLAGS} ${LCOV_COLLECT} + -o ${CMAKE_BINARY_DIR}/coverage_test.info + COMMAND ${LCOV} ${LCOV_FLAGS} + -o ${CMAKE_BINARY_DIR}/coverage_full.info + -a ${CMAKE_BINARY_DIR}/coverage_base.info + -a ${CMAKE_BINARY_DIR}/coverage_test.info + COMMAND ${LCOV} ${LCOV_FLAGS} --summary + ${CMAKE_BINARY_DIR}/coverage_full.info + COMMAND ${GENHTML} -q --branch-coverage --demangle-cpp --legend + ${CMAKE_BINARY_DIR}/coverage_full.info + -o ${CMAKE_BINARY_DIR}/coverage/ + -t "SolveSpace testbench" + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/coverage_base.info + DEPENDS test_solvespace + COMMENT "Generating coverage report" + VERBATIM) +endif() + +# debug runner + +set(debugtool_SOURCES + debugtool.cpp +) + +add_executable(solvespace-debugtool + ${debugtool_SOURCES} + $) + +target_link_libraries(solvespace-debugtool + solvespace-core + solvespace-headless) + +add_dependencies(solvespace-debugtool + resources) diff --git a/test/Gentium-R.ttf b/test/Gentium-R.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3b44ad16d4b7ba32359857f73142a01d11687ec4 GIT binary patch literal 362644 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⁡v!y+nU!5NkXXIK`TVOem7WvQHD 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

$Js7$j;=G7{{ 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

}IQ3yn`RY-S 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

!a-aJ<2Y?`Gu%^5ytw(DF5Qml)dyxWiMZ?>=m%R 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 JMTgLr{tFoz=urRw literal 0 HcmV?d00001 diff --git a/test/analysis/contour_area/normal.slvs b/test/analysis/contour_area/normal.slvs new file mode 100644 index 0000000..15dc34a --- /dev/null +++ b/test/analysis/contour_area/normal.slvs @@ -0,0 +1,512 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00070014 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00070001 +Entity.point[1].v=00070002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040001 +Constraint.ptB.v=00050002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050001 +Constraint.ptB.v=00060002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00060001 +Constraint.ptB.v=00070002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000004 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00070001 +Constraint.ptB.v=00040002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000005 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000006 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000007 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000008 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00070000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/analysis/contour_area/test.cpp b/test/analysis/contour_area/test.cpp new file mode 100644 index 0000000..748f0eb --- /dev/null +++ b/test/analysis/contour_area/test.cpp @@ -0,0 +1,7 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + SS.showContourAreas = true; + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); +} diff --git a/test/commit.sh b/test/commit.sh new file mode 100755 index 0000000..d49708e --- /dev/null +++ b/test/commit.sh @@ -0,0 +1,9 @@ +#!/bin/sh -ex + +make -C build solvespace-testsuite +./build/test/solvespace-testsuite $* || true +for e in slvs png; do + for i in `find . -name *.out.$e`; do + mv $i `dirname $i`/`basename $i .out.$e`.$e; + done; +done diff --git a/test/constraint/angle/free_in_3d.png b/test/constraint/angle/free_in_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..2fef8c185e0a3efea5f6b4e411d23675eeaf05ab GIT binary patch literal 4763 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 Usb)9`KRSWUCKkr54QSB60EA524gdfE literal 0 HcmV?d00001 diff --git a/test/constraint/angle/free_in_3d.slvs b/test/constraint/angle/free_in_3d.slvs new file mode 100644 index 0000000..dd9a053 --- /dev/null +++ b/test/constraint/angle/free_in_3d.slvs @@ -0,0 +1,355 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000088818 +AddParam + +Param.h.v.=00050015 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000088818 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.valA=45.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=0.51894056308654845644 +Constraint.disp.offset.y=3.99584233576642278152 +AddConstraint + diff --git a/test/constraint/angle/free_in_3d_v20.slvs b/test/constraint/angle/free_in_3d_v20.slvs new file mode 100644 index 0000000..7f118f9 --- /dev/null +++ b/test/constraint/angle/free_in_3d_v20.slvs @@ -0,0 +1,355 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000090000 +AddParam + +Param.h.v.=00050015 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000090000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.valA=45.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=0.51894056308654846000 +Constraint.disp.offset.y=3.99584233576642280000 +AddConstraint + diff --git a/test/constraint/angle/free_in_3d_v22.slvs b/test/constraint/angle/free_in_3d_v22.slvs new file mode 100644 index 0000000..488f552 --- /dev/null +++ b/test/constraint/angle/free_in_3d_v22.slvs @@ -0,0 +1,355 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000088818 +AddParam + +Param.h.v.=00050015 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000088818 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.valA=45.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=0.51894056308654845644 +Constraint.disp.offset.y=3.99584233576642278152 +AddConstraint + diff --git a/test/constraint/angle/normal.png b/test/constraint/angle/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..40b2d0e190cf5f5c833be4f3730398c704e3c0cb GIT binary patch literal 4882 zcmeHLc~Fzr7QYDz1Q29*K|xS(Nn~paC>Wu#xD*f-6Cl8+fD51ySt?5*1gZiGrM3@n z0n{fhBoM&J4j(F_(z2)oP+1fOWDylYBr)V&R9bl*Tj$O6k2lkqWNvcrIrlsFEWh)+ zU&3Zbdj+}qasU8@4eM>T0Dz@VeF@TVrfOIEBLGy|H`rKjJ9uiiV>q?P?q7$RZ`fFz z3HA0x=bmF|sveMPzIhbmX13jvG;VLm4Y>S9%$$>9z7%WBHjrCic)x$Q0k!+So4Y0e z#*&Mk0HAwt8o(+Y#R7sVeAEuCJXrJ%f&uZeB)~9=#RDaM4glBwE}^bF$(D`mmGzC1 zsK$vNuT`1iht<;`$zhh>L(A3$t-Ff&<*NeQ0 zfVTV5QM(iY(9!?YNg2_<1oP#9mhSYKsUFBYz7&Wvsd?`pv?MVv{WD#YtzEyR5}R%I zM>E{qbLU`yk&5c4d~CJ0w=@C|W-Ct{$}->_6=E3H86QG}&}=Ecd}dIZQhYCYVwz4u z-^~bffFGOPk_5oq1ldoeK(_WMtrE*POqe!w`y8sB7@*nTd~lFa-dWc6eBF!$Gv)Gl1GWGhy@C5w@-?M|=Xqk#b-$fBSDth(8X%jeBRQAStp3G}V1GKL z$GlxHQ@~hGe}&F;nBNJXbo2M;>jGf6lliH%@2|6@ z7A1!QxHyejRy52|63T2yATq?t)TV;#wJpDU&iaDl912(n=XL0KP9Ro(_2N7*9_Ys| zr7MPfO~Z`U;N(~9dPHZu#t6b_Qa7v;^C2gIF`*a0+FbK>-6<(!n_Rmxz`3 zT#^S$?2sDy8!oBS8DhDCh6zeRnM;olftK@BzJ}P*FfW&>k`7L7G7l^N0$RL~ShiE2 z(=AQMqr@r~Lq-*=nzc_j8nK5}9VKpmoF-|Q=-EG<|0C3J!6ubN)`ZO4D>%z$!v6~m zl&|5TUO7%f+f2Ml9GdX*fnMzj7)D$gA)NYpOXu>!@sPcnX_y~$IY-J*ZS+W52E{Mp zGz?2kzY`7!#!WAkLBrRNYLT=Giv=6cAIWVXZQ{!@I@L~_;WV6FV_ah*;)EKHLj$WX zc_$LV)h8-CvnK}Z&bWJwTMP@-wxrcaqUUX{NP_|b z_W7}oP4vz;4c*5aa~WHr@8RW8AxGkXN`(0G~P*t zI`n{RR^}Bg?)8R?+WMUSYwRD&)L~#5a@eF7w8-nCu4pG3X7i1G^ZOf~4Qn=Z#qnDm zHrKYKzlSUOIp;Ed3vTdr>y(NYe&4iR{WhlZ#&&reCl^B@>QkdaFN)8nSI3NZ7 z`jodmzt?{odu7iZHF?HLSyI^Tr*e!;INH-NP34!ky{l!*Up+5zi*fCUS7Vr~lESV- z0cW_IH4S5=5^Gy}r{wj$co1NXINyOQ%Ce+8Fy-v@ILitylpM(5ES|8IaYg0g7*!Ujj$n>dJVLuMmt`g$U6Uf3rV^d zBNlnZi1^RfP$N4XFSi!d24lNN->T`*`%+ut?)52Ul8*-ox1ItowPJ0PwZ&a?x@I#> z43rP-(ccc@-2I}>?cRg7V*QvLgFV#{IA!RHGJQFAh9`!zQk7<?JLD%K%%^R#BeppyO$O7p(F6H4xcv+U zT!qKoF}0~WTt1rHpO`VDZOw$#8SLaHm!x-CeY$796Les6xe>}>_5_cBR`Kr-90(@2V;I23qy>)<{)ujO%Mr zb+MDEI0lxMBu$i=#`{-Yvf$wtjc3{f_MX=%UPbop)naX53BK>p19YI@hoH79Ccni* zLdZMrwYKU+@u?h2S0GvL$clkuf!b?zQH+=uuf8Aw#R`oDW_(jFbEhq(9;d+x>`TK~ zMEb{GQeTjUVhLYxv5#^viy&CDT_)9ZP=uy}b9~oc|B7EhO7vTt-t|dFbb6FjL}4p3)w>o&+q|AG^J%=%jJJj=9&^8vEy0|DqieB10L>%SS!|CB0V zvGE78_or<5A2+8f`+j1Wmmqrn;p{`tDt_`OsL4>9ZF(d z62CQ0(3Jt<`M8TB);K#ihVC)&ZCS0Wx zt#is#`fDCUD;ff1?afk~gI1OHy5Oi^lPvF8dqapoxjb~#+dR zh2u43xM5Ju#1|%uIAOosmjl8joUmI{qdq6>=G18R)!N}|;)TFNC0IuELIIg-QF;9B zv$`IzJ51Q8(S4UL<&VxjA`QkLm&CT5xP=9#^dZ#G!3P8A*xavD@xVrwr3#050xQ_I z_?`^7S(uNOom%8^a#_cTFV~c%*jHC^pT6?yp!g~LobSsQD1(L}B1bE9_&_J6`rE;~ zU6v=^texJ5<5xHDMmk=%$iB(FW%BCQXqq;-!i!L3)$fV$nIAgOg$aaDBy%xSjbf!Tm+;nH`A@nqp7g1;4U4bUgF9HTJZqViH(hGT$zj54&{PJ)j~2YvLo~7gJR4@ z=Yx6V``<0M%u+PVXAfVQ2&~yv6k2%f+XT+W+Ob)n?fipH@dD=4(*>Q23yNwdx7K~P zlsD8hY2O+1q{8_1ptoc^P?A4v7T~wm&XwbJ?opl;w~N+e};3(a_w-GPh7mk%W-%rL+_CO?{s4oc|m+FX!QY-{*d==enQ!y58h1 zZcZvniWJ(mMdqvfb>kep~c;UT1sp%Z;hW8vPP|YA6TH zH_AVvzTE&b*9w#S_2F$;fO$E4;j<$SDk;6lS5wsAT?$au4Of2Dt6f@b8${t;XGR_X z;Qog~5)6P#8qg%W;L1SHg#ZHy^Duy7Ohp29Lk0kM5olnS&Vd6(&Htw$@fKG9{(zwD zJCb26Jx5YI9oRh?H2X0RTXo3A<7u3YwYBi@`;M`(v1q}s6J>v?>;Jc`C~;ce81(^M zWB)Ok?B6uU-Px-OfWG0^)&>}rq!RegHw9^WcnO;D_3_M z&xYrkJLY^Rv(XF>A`j121e8Dnj&>x-bZOX{GSoR8IVQ ze9hTGl-)`yQcfKUL9S3(s)k z4WRt+aAl<=BT6y!)O2VxhZC;?O|nNF$Iu8xhUmbR*XYr~Nk>9;9rTp9iWuCfQl-p( zh}`Vu!VV~E0Tcy2V}~@#m9QRqMo+V+)ty*014<;F9iC0TD0^e)k#$ch_{bnc}!_SAZ&(#>7*FouIS!S&2{B_mJr*OvW$8{5e8)1|SB{VIqx%_~EZoUN)F zTZblcn(pmy&Q3A{UHS~%8_Q#)q>EY*h{YFws#&Wr3~Vj_5IkCTlVxj-b;QHQ=20Ky z9JsobX0T!rmbN-qFOr!cG)1|`2($KbNnb^GHGXiP;+`I)uB&d`btsgBws-nkP^ zj}~A-LE(rFCc25Y%+R*%RYED8pbSr;7h50)xw50z+I0c@-U8(_^5^qg3*|XwOtUg_Z6Heyg0wd*sZ$d5|4*(5gBBf@jOs zRSe7W7vq1Xmq(RR($dsQEh_n&pLBRVX7eyFBGpXzkNCT)Cu!vofD+Egj@EJ&yjkwD zKrl4?lxKl;id6%xYE=H+?7PXL@3R_K-2~T!VJA4G-6^Hz(`(MB$8_fhD<8*z6>Dq7 z$_L0V<08VQE>V6-e10i6I*MJNEx7KtX%$=R-kv48U47N5(!}uE zt;OTD5p)MM@Cr>b_N%>cjj4+D_6-@onPVdw5{+%f^TakV0tQY5X-1g>EsdOwTr9m| zBo+f$z3P7vSk~PGO}MfGUMJHKnp`@p$B1q}ttsf9>Q6(^VwB+xnpD(Hzl>YY|GI8= zb#*eYE6$?lIjJes|2UG5R^|!ej66MJ;#cH9 zv`*X>Cmw7W@&|T{5*5{YUmV&|9L|{Tr>&cA(ItPl&h(w0a<-BP%9w25nsHrnrJ989 zv+3EOubi8TBns;290T~U9;=k^Dk?mOhuMWE{fhzJ^4NF!LzpTzTUYz}Fz%%pjA5gR z>7_5tS4gwJoR;4nY|4`(?1QK1_=>x=*QrrVRW-u)8SVf_GKp0>OFy;wM1K;J<04VKvc(t*oVNzwtwpNp*U4FI3u;3laY7RTMiV|B2w;|vf+f4(BBKOjT%48NRw}< zy%WT2=SZVjJ@PToiphQUJO$uh^>f=wh8#iVZ(r@3U+7CnLVV6eS?^W<5E4}AGr)!_ zX7^I(ebpzmJ_?HU-EERYw6rr2ka;^p0^!Q7CEfJz1=FOOD)C#VQ^=a73`*RG!aKpb zzPhGbp3+W3kb@#IQZ?aM?`xA!&K_?kGhN$D4!8S~MJ4Uoueq=NMWU&!mMw4EPYcGH z1cA%jOT?D~@ATZdu!dnNG)5f9=Mc@@?iFq4$&xxV<~C4XsV!}lmzS)POdWX&=Lxa(2~()I(z1z0=c*=_^G!V~uK zoTF0Z5}u^~1rYxrwDXk<_OEC+1DuQ*Y1p84Cb-A@@5=2T?wr+N3tOzy-t3r_Yaat6 z0}8Y9wPC<`fu@YykriQ|rQvfVclBA`VnA@6k;wyzwdP~=zpL2)i7H@W^1Qj2iXk{L zqb^bfFbro>p1AkwyJ+m+1--1el97K8&FYs`pk#D@mM5_vVktOre^xYp)9*x3&0PH} z-VX;DRJqsa-%Dx;%#o$kyHA*%&+7#zof2Z zF4p4<+X=a_yz7B201#*VkKH<#Dryw!?*jfSjD}_;;K%Vdy=2b1x!ABT9PtiqT+=od z5m3x#ykM@bVK=AZOEY8TLx2hmBG^bY^?Al4IM@;K1S)&nsn4!u7KDFd25(O^sy*Y0#^7Lb&w2Xg+Ico>2A@fY*wD;6yInF)v7QNa>Z=vps!NC<+>I9K#qyg>8W#*R3J+uIyj5Rq>HrGF=u#c?gX3_ zCzs#~lQBTgjleX#2`E2OBlMw*IyItjAWR52xJeYQMDwzf=9Rr?@~*Vwgu_JV5lK_f z74DWPpB)k%SWjIQoOsb--_)jTZE{aIj38FfrecFOMl0CJh! za6;$CrqlbIgSO3s52Q#;;L^jPn3ieNee6%RZ;Y0`v)Z_~!i&^I`E|+VF~c#;tl^f) zA&a88ph3&vPP(X3zulcB8jMV>3NB);dgT#BO+cH{thSj>#$(vh33ayQ3#bwMT~^k#1KFw~TFyNU&*>V7teeEKz`HdpoR0 zeNqEQGuU6$I6-fxkkFnqpK!j2!JTf2^>(hpFt+hUYIazmjlyu(Armk6MO~da-It-e zAYM7KP848&71VlmmHlE}AEb9!`3Q#J?20sN&Ho#rOLkJ{qO*+N*eERwu(lbodXX$gHlpQUnk~VbMC(P?EUS%zw@0N z?O<;uCc0D<03c?)$$TpS5P9y4U4V>~?M{9MfHd3M+;rRF%Y)B56JI!mL^Z@Rt{ShXAo1wf`0GQ|!E2NZ27C~!=K0O(o_43JcX0r(k>2PVmE2w-LYM@Hf5hNBw_@(Ja$ zeC4EeLG{Q$`|RL{Px~&7mRCiu3Ux0r+iQa>cjp@2kxkKaQ$qihKlKS2moU&Wbpv1Q zyl}2^$FvUufL&AZxscv^rDl@=9Q`roKnC@R1Xx&&eh<)#Z;E$*AvE+O1e~mbA<~lz!u^3Gr1?2nJmG2&0o6*^&>~=^s4TVr8iB{=(wvt-7@> z2lU!C1aF*IX}WohjeQmX#NGV>^6#p!J|URpfnuwEoQZ=uzl8|2R(D~A0w#}1C;q3EVX z=Gb@LL-&_;Uo*dNitL0I?sji#bfG<7C!2%rD6xepiVFT16AcFDXkncCV|BU=7)XxA}F~KIbeFczd21Zy78CHfn*mq@3($IMMvxQJ^5<5~znxyye;J7Hz zl_L9Tb%~}&Ksa%8H&omeO(c>|xh_$PyeiobAV&g(i-}gJFMAh}!zRSWnqn&DkI~X( zU@(ZA&1ihdg+gd?8aq-?ngn_j;SM`wI2cPfEaZ>z(}Zw`R9WDW6{RBn?)K*YltG34 zoRtWnnSsQj$H4695~W|y%Qv3Uq{tw!&{WBi$}BLc2rHVnq5P~V6>`z)fF(khg#0mV zZ)8%$6~9<^q}<7Dr7^csh;E0*Z?JYly?Id3hjF0seS98!7AWuF4~{+k!ChdO)E?v; zQ77M%%ir%@*Qir&=7xeT+$puW5(ftFW@;71-?nPVAJ;G7=2F|ElZz3To2ymK#A!TQ z@%mFCddQsGXuOn_$Q7fLND5?2*IQCjnzNHeS>Xd-e4kT;W2^4Uf}A%>LCaf5A+Ec} zSb*0zhxl~`Ws7H9bYj$jhYDOs>&MkSHioYESqve;=J-XQZfxZ?ISfwyJeFV+H__F% z40(npH8OCkRT%Th`B3LUJ2%v6Mc978U}u(^Rg$qPb=WPo5K^?Dq-^8Rvg0=+mtX3T zH*}{Y4YdVkZb5LnD1r}CIS_REyxW<=zPN-YD0TZ>vEOcN3ysLmktYVnE+)4_@Sq+`SKq zJrItcJ5#=fBV-;a>&UG+j{M8is>q-zBqI0gOEWA4vmBS{gtYQSBruaP*V-90fnHJZ zNdma03imdSQ!AJSet=#oLMRUC*C8q4=*p7hfSv|is|6l#(0KQe1EwHk&LwSsY7E9i zg<&CQ%L2X8?5;N+r_>z@APY+nAIv_Zo~V;dz3zdkzmzc+#qNUL@aS|B66@ys=f1{+ zVO3aP{tQAENPo@b7@fH3?Zzx;`tWRE6ik#MhSVTK(Ztv!-zOXBtH=-z>G7LZBKEQji)RWE zf=vF%1TiZK1*mBJmZTlyBmLvVrhHaas|&50cDTw*{!zqgg!-DzDgO3?EDwb0WXR_H z7iogY0?&w&1vT6@F}gvU#inP}T`2Yv5yE#R;{x)`22fpya9SMgdho%xc^tccA5$#E z%8a7l?n)zO$)VmPi+qV-=!0mfHR56bR{J~j4esX=R8GkL5p`b*^JVg{d7#-|s`D=Y z;W7ZQ*|;0QnuhBDI~7^Z%*sfsf&M}UMshThA4j-?5#Abb50OM@{vLsX%N$A3mnHA6 zC=wxbG|!ea8&6LQO^=jE$U6HOL%80mc3$YjNVm$yE0~@YFlb}1!#$$zV=`5&X8W>v zNh;+6tLfVj(**+lL`3Ou@Lr#mLnXs?8=`g+^EaHxG4^(CDd(qA1(=?wR(4%`!R6T-&ztOQ$*PPrHJ}nj^0+j{OYjN zr)&40NTC-l=fAHMou@3WHnzHyVgWrdd7c3H%tLNy!mo-3>H!FSuX;>~wq*=&L<0Us zq5P?K)nZY<0M560XNjOaqA)m=>EFZGR_ z#^}z)q3A;W+Hjc3I^ahMU*JTmv}|mBbchxX7JpmL;IzVtoQw65--_|3mo;x`2+ka4 z@AD~qaIrN<;moRAAf3oZJgKyBq6wBeo8bU&jU^?It9@&M*F_8n&%)iPP_++TTMN<4 z!m>Ts#zQI(-o7ymf%82paX>%bUsEd8VK@;BGM5HK272df8_mR57K>*sR}cc1E7BP{ zsWpYtPQY(J)Zbyt1Rc1Tp|y&z_`@fnfVUDBe=v7cg~jjB9rbjExx}*x3Q>g+{Yc7$ zF^P;MIZt;4sKrUXE?E1X;@q zPS9)LR6Az6+L3Z7rB@psni@dQIPbb}_~lfk1aP4l2{#6=^SNN@ZgozPUN)(qJeU1b zUSZ8+fvdu~d9G`2kFRZ~o-Q5xwL^W&Lh0ybFZE1EMD(|}jE2_@9L%X2>kS<;I-3$l$r2y)|{KZ?=;J zu2a=Z3uZ6xpFWTpbg7m*;1tl-J0ab;BT<*Gp27 z+$5HH9t~XYtly_0r>WfE$kSA))Zj=R>Ywxse>BlLoXIB*3ez3+Eh*1qe#o?ZoXZmG z^Vst|d``f6ug9N6Ny|+BNW~~*br>e_XO}{=}H`w4M80hiq4-R$e zni5X8>@4DKoA~j~VS{JLyOD(22psYT=&9Uv@T=fR*6&w$xuth69m-PEYrxr*eWgq!a(x9{LFrd`FqySXs5qv c1Pf3NOBWyQTxL>+ywU>J7WU>WGfL#Y0A;QPU;qFB literal 0 HcmV?d00001 diff --git a/test/constraint/angle/reference_free_in_3d.slvs b/test/constraint/angle/reference_free_in_3d.slvs new file mode 100644 index 0000000..bfc1723 --- /dev/null +++ b/test/constraint/angle/reference_free_in_3d.slvs @@ -0,0 +1,355 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000088818 +AddParam + +Param.h.v.=00050015 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000088818 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.valA=45.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.x=0.51894056308654845644 +Constraint.disp.offset.y=3.99584233576642278152 +AddConstraint + diff --git a/test/constraint/angle/reference_free_in_3d_v20.slvs b/test/constraint/angle/reference_free_in_3d_v20.slvs new file mode 100644 index 0000000..8a6aef6 --- /dev/null +++ b/test/constraint/angle/reference_free_in_3d_v20.slvs @@ -0,0 +1,355 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000090000 +AddParam + +Param.h.v.=00050015 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000090000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.valA=45.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.x=0.51894056308654846000 +Constraint.disp.offset.y=3.99584233576642280000 +AddConstraint + diff --git a/test/constraint/angle/reference_free_in_3d_v22.slvs b/test/constraint/angle/reference_free_in_3d_v22.slvs new file mode 100644 index 0000000..dd91a68 --- /dev/null +++ b/test/constraint/angle/reference_free_in_3d_v22.slvs @@ -0,0 +1,355 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000088818 +AddParam + +Param.h.v.=00050015 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000088818 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.valA=45.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.x=0.51894056308654845644 +Constraint.disp.offset.y=3.99584233576642278152 +AddConstraint + diff --git a/test/constraint/angle/reference_v20.slvs b/test/constraint/angle/reference_v20.slvs new file mode 100644 index 0000000..1ba8cb6 --- /dev/null +++ b/test/constraint/angle/reference_v20.slvs @@ -0,0 +1,366 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000090000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000090000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=45.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.y=4.50000000000000000000 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=135.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.x=-1.99273176225234480000 +Constraint.disp.offset.y=1.12091161626694390000 +AddConstraint + diff --git a/test/constraint/angle/reference_v22.slvs b/test/constraint/angle/reference_v22.slvs new file mode 100644 index 0000000..0408a83 --- /dev/null +++ b/test/constraint/angle/reference_v22.slvs @@ -0,0 +1,366 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000088818 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000088818 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=45.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.y=4.50000000000000000000 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=135.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.x=-1.99273176225234482928 +Constraint.disp.offset.y=1.12091161626694391096 +AddConstraint + diff --git a/test/constraint/angle/skew.png b/test/constraint/angle/skew.png new file mode 100644 index 0000000000000000000000000000000000000000..97fb0dbf53595dac63eef4b79d78fe3bb110b6cf GIT binary patch literal 4869 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 literal 0 HcmV?d00001 diff --git a/test/constraint/angle/skew.slvs b/test/constraint/angle/skew.slvs new file mode 100644 index 0000000..e793f93 --- /dev/null +++ b/test/constraint/angle/skew.slvs @@ -0,0 +1,340 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=00020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.valA=90.00000000000000000000 +Constraint.entityA.v=00050000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=2.50000000000000044409 +Constraint.disp.offset.y=-2.50000000000000000000 +Constraint.disp.offset.z=-2.50000000000000000000 +AddConstraint + diff --git a/test/constraint/angle/skew_v22.slvs b/test/constraint/angle/skew_v22.slvs new file mode 100644 index 0000000..e793f93 --- /dev/null +++ b/test/constraint/angle/skew_v22.slvs @@ -0,0 +1,340 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=00020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=120 +Constraint.group.v=00000002 +Constraint.valA=90.00000000000000000000 +Constraint.entityA.v=00050000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=2.50000000000000044409 +Constraint.disp.offset.y=-2.50000000000000000000 +Constraint.disp.offset.z=-2.50000000000000000000 +AddConstraint + diff --git a/test/constraint/angle/test.cpp b/test/constraint/angle/test.cpp new file mode 100644 index 0000000..668394d --- /dev/null +++ b/test/constraint/angle/test.cpp @@ -0,0 +1,70 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(free_in_3d_roundtrip) { + CHECK_LOAD("free_in_3d.slvs"); + CHECK_RENDER("free_in_3d.png"); + CHECK_SAVE("free_in_3d.slvs"); +} + +TEST_CASE(free_in_3d_migrate_from_v20) { + CHECK_LOAD("free_in_3d_v20.slvs"); + CHECK_SAVE("free_in_3d.slvs"); +} + +TEST_CASE(free_in_3d_migrate_from_v22) { + CHECK_LOAD("free_in_3d_v22.slvs"); + CHECK_SAVE("free_in_3d.slvs"); +} + +TEST_CASE(reference_roundtrip) { + CHECK_LOAD("reference.slvs"); + CHECK_RENDER("reference.png"); + CHECK_SAVE("reference.slvs"); +} + +TEST_CASE(reference_migrate_from_v20) { + CHECK_LOAD("reference_v20.slvs"); + CHECK_SAVE("reference.slvs"); +} + +TEST_CASE(reference_migrate_from_v22) { + CHECK_LOAD("reference_v22.slvs"); + CHECK_SAVE("reference.slvs"); +} + +TEST_CASE(reference_free_in_3d_roundtrip) { + CHECK_LOAD("reference_free_in_3d.slvs"); + CHECK_RENDER("reference_free_in_3d.png"); + CHECK_SAVE("reference_free_in_3d.slvs"); +} + +TEST_CASE(reference_free_in_3d_migrate_from_v20) { + CHECK_LOAD("reference_free_in_3d_v20.slvs"); + CHECK_SAVE("reference_free_in_3d.slvs"); +} + +TEST_CASE(reference_free_in_3d_migrate_from_v22) { + CHECK_LOAD("reference_free_in_3d_v22.slvs"); + CHECK_SAVE("reference_free_in_3d.slvs"); +} + +TEST_CASE(skew_render) { + CHECK_LOAD("skew.slvs"); + CHECK_RENDER("skew.png"); +} diff --git a/test/constraint/arc_line_tangent/normal.png b/test/constraint/arc_line_tangent/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..ab59db735cdd656b8eff17463b758c7b6c2add85 GIT binary patch literal 5100 zcmeHLc~n!^);|dW0ucd$h>8MH6eWm=2$s<*QbiD@LNEf6!6}F!GZ;v47K_>{Ajt4R zg;ESeF@PxHT8ya3BoI^P4~sG@GRgeTO=4T?d*9c8-XCwRw-&6Od(OV+?BTb6XPPn}OOnj#p?(Vew#K+)MiX{OKYIOY?a_x^RUUX$|O3dLFZUn${ zc5f*F%sM<7FxRo9fznD20Kedfz&a7Z0=x>`_yCpww06=k;I!PoNy+khN_Z-j8lM); zsOouZb;7bD^yH_%>4mO#%ZB#{x+^MPj?&CqvTWJ1am$N&OA7Ir|If~m%k6p+ znFpMlnU6O%0dP9;GoQwCA}}1YcFT{#w+U=4Gwz0XHeGXge<_xk{Cj|$7gDy(03>}U z8mN{mOq*-;+)#aH){!;ta$r~TTS0tRSGMsZQSkgd-R$#0SgJUb?@j9Uz#egIV-7}0 zTgQvXHBx)jz!5B=+xAfvmQ}6hW`ZN0gYyVctlP=inGb}|8}?Ci7h=@l7}fAm=at85 zfVx1Gp4f83QY?)wb}~RW%LP1)L9mmCHH~_p_yW?o4!$0yh9*~J`@<`6yL|5$QL@$H z-Uv%=AW@uKel67fNlT1@^7W*8)m-sVHyW#Z)6n83O2Gm?o%a3WPa z!5_Tgj;US3k{JqIT0IqV$oBo1 zS|hPX!lkJ4Dv7cu%ncq#ti2qb=Z-<_al{7);D|2j9L0^cXf*H_xc5~Fyq?3oIX5zv zN>Xfu60CUN8c2cX zcyfkA`3`T4W8UEkiTN3`lQkZP<`#jt$Dzt68cVItIZ`nGLD<-vHCBU+E*`WkMgMEh zHb1gFuwBJD4{-)qHs@h5rnk_Py&wv#@Z{8k5&TNB-KadXO%<&Ol#xikjA@}dL#oZE z!28@B?i;-oijf2|#Ky;Sa>QLKF5tZpi(l@mh(*FBkrJuuvG`liK_6IU73MDapkpOA z_S~<_aLi#GF|@z}4>WTL6Lv;O4o}oiBp*!?HH(kUBEssN$u*g7`ASwUm{!t>O?UXclB_W#M`M3DS zxCaI~CieJ`GXBKIXh|r7dA%^-%Kj4SL<1p>fNa4LFLg3jxFubzi5;h5d|wc?B`jZHgU`IHfOmX?`-ADzHT_m$T5{1! z`SXF0ypK*FB?-%dXND@gld?Lm17!9K2QG}mvKaWM4zzR)L{R|ZBk|(7c*g*vqN5<) z%*O1Xgv3ma2gx!oOE}5xp&|j~iI1Y#b77i9QF2iB4;{9InTIFUQXxXbRKhRcnTP7}>ra|!gK0@NQ$3TFK$H&W zJjed2-HX6cdEiIlge9#eq8iFx7a=wqq~mv6fpxknWRkB67O}z+cNfZGgx52c7VOeF z6fbnaKr$Q*^e)I(?ho+76Hd?H09vM&ZOAl zIejrzi*memW;Cs=de#UQ@H^6Pw_!;aQ4?pUhL-$#Mhz_&)4mNEf_6df?#6vnUC#54 zk3T-4vIoRHA!;WpJA-S~qr6SqV4;VG%61&a5tG;Ru^o_a3=UkBMy@tUJJ+S^<{yH; z>k?^@MIbGlK{|DI-T|fySbV!1o+!n{CK$q(AfFkairqM(OY4Z&2|tT?7zJ4^jxM^? z(@(R#SbA!uLzQKsCuZFm74i;Lu4mb-b|Qm~d+&LzxY#ZGG-!19895@fDnmBf*#HO?ST-&@?+6R>2W-9N^o3VZW``%w zo$g1k#&#XXROoQJ<5m+b)>XxLsZfIWs(nmeih1=+LJF5EJ9$jznx_Ht83e&hN% ze4a^~2QMl6p?y9t>Z7amx4Vy@jF?!Y{f&2BtW`261|}hhp0x6Kh*i@4vYetEHC4SP zeQ0})D&0T3nVC|`MH=Me>}YkyPUNwt4f6iUGH12!3JC$y_nar(PAhpQU$Nlm;2$be z=Kb&7yI7sOK!~B9p0}BT;jF^O{!f?z5kzWn)X&0vM{0A_VRS$vj<^NQuOgHXpy}Tu zo~96s-{ul-hp#m=u0T_vrb0gJ4W$kXb|rUYygn$1=|~-g;a(iE90I2UT?oqb7^G7P zrYa07zelApi+=zj@%=p>1j_a_c@`O0l!;|Nis}eex@wa}vhlQOJG=dIwP{6A} z9k?M9kMi>djp~xTELY(B1Q%NlO{D2TEqGAT?4U#;OMq`n#!*YO>}dgqp$op7X`9hU zJ6b>(eEhLdT40`#vbY*w_@cC*7lKG5Mq1P4R8v!tk>>RDhb~a7twJ{SLOF$5@#n6b8K7_X?1=QI z_B}9JCai|74#HkG>A%s?OR@lvcu|uJI~~Ozj(51Decm63-2Zt)xI`Bf z?3X780NXspo#&tDzPx5|N|2cNY0?7({o#|BNv7hq1Vjo`HJn5H{ENsx{9AM{@V zgn3ko?z)u-*4>=c{~EQ`2bK#}lfu4k5e}I8{mWs`B|}0RFVU6cd_u(Ewv8bZpXP=K z{ggCTm+V$4U?w<3<8JizW>Y9zUXK~F&h&B{K||E6 z;RO-_Ox8!kSN3~z$LR2c1Kkzstj4CxPZwW0LIfi@YdJE>rpKObfO;R`mAUz&?&hrA zw7OH=84Y)UL|rPd39~M>C4oyvYvly81t~qfhx!Fm>Agdn0HQo^G3E89@AM8xgQ=CA zkm?3`km?wefMIdHvYbKfDi(JODot2iBUF~Yg-=x}xq#aI*KM-&?9lI{=UxXe zRm^503Gdqir09sIk^<;250DZky;I0)OnQn1oj7MYbmja4%5fNHB@)L$t<^yjs8{l^ zeKWY8ms@)>x7V{Pibr2l?NaCJylx2`C-yndSz?h33~2#-?XkBhQ=yv2W~vHu)J7h+ zyQHfyWCXU=Zp<2+_zSkF22r4PU*9H9n{B;*{ZHe&b@1s1v*q|=m%3mtO{@M1hHlD~ z@RoW>aPqqiPu=eufBdE)h1$-2ebcgto)$RZRg^beI^M^-=QH&*K+~d{o!`v$iVXyV z_Xgh>)@aU6zMOj-U-RH?BGn>|o;F+;c)x4d@~iez&B)(^OS;Al0$Ob6Zkrc8p+={f z7c#0hnIlwVw+9 z%2n8>ztCZM-^)pdv~rzB<&d7~8r~b~(($ID+&C#j8v<~x%}e*p9`|=ry_vNoBR6wB z&atb@x`s=-lBNCj&P+H{k|*aMuG=AA%&(gbi?MdW9y#t_ literal 0 HcmV?d00001 diff --git a/test/constraint/arc_line_tangent/normal.slvs b/test/constraint/arc_line_tangent/normal.slvs new file mode 100644 index 0000000..7572139 --- /dev/null +++ b/test/constraint/arc_line_tangent/normal.slvs @@ -0,0 +1,450 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040016 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040017 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=25.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=500 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=14000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=25.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=123 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040003 +Constraint.ptB.v=00060001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000004 +Constraint.type=123 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00060000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/arc_line_tangent/normal_v20.slvs b/test/constraint/arc_line_tangent/normal_v20.slvs new file mode 100644 index 0000000..402d9de --- /dev/null +++ b/test/constraint/arc_line_tangent/normal_v20.slvs @@ -0,0 +1,450 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040016 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040017 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=25.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=500 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=14000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=25.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=123 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040003 +Constraint.ptB.v=00060001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000004 +Constraint.type=123 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00060000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/arc_line_tangent/normal_v22.slvs b/test/constraint/arc_line_tangent/normal_v22.slvs new file mode 100644 index 0000000..86a7d82 --- /dev/null +++ b/test/constraint/arc_line_tangent/normal_v22.slvs @@ -0,0 +1,450 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040016 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040017 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=25.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=500 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=14000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=25.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=123 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040003 +Constraint.ptB.v=00060001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000004 +Constraint.type=123 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00060000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/arc_line_tangent/test.cpp b/test/constraint/arc_line_tangent/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/constraint/arc_line_tangent/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/constraint/at_midpoint/line_plane_free_in_3d.png b/test/constraint/at_midpoint/line_plane_free_in_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..9f41d0f858083c5306934fe5631e26329cccd5aa GIT binary patch literal 4333 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 literal 0 HcmV?d00001 diff --git a/test/constraint/at_midpoint/line_pt_free_in_3d.slvs b/test/constraint/at_midpoint/line_pt_free_in_3d.slvs new file mode 100644 index 0000000..f40d499 --- /dev/null +++ b/test/constraint/at_midpoint/line_pt_free_in_3d.slvs @@ -0,0 +1,315 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=70 +Constraint.group.v=00000002 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/at_midpoint/line_pt_free_in_3d_v20.slvs b/test/constraint/at_midpoint/line_pt_free_in_3d_v20.slvs new file mode 100644 index 0000000..63806c5 --- /dev/null +++ b/test/constraint/at_midpoint/line_pt_free_in_3d_v20.slvs @@ -0,0 +1,313 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=70 +Constraint.group.v=00000002 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/at_midpoint/line_pt_free_in_3d_v22.slvs b/test/constraint/at_midpoint/line_pt_free_in_3d_v22.slvs new file mode 100644 index 0000000..2933389 --- /dev/null +++ b/test/constraint/at_midpoint/line_pt_free_in_3d_v22.slvs @@ -0,0 +1,315 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=70 +Constraint.group.v=00000002 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/at_midpoint/line_pt_normal.png b/test/constraint/at_midpoint/line_pt_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..0e43d59a86ebede65c3dbbc7f22c5fb364216db1 GIT binary patch literal 4298 zcmeHLYgAI{8va03l+@C~E(%RecEd}Vm!iZeFEv^nCnLmE%OaS?%#10BG9qg*8A{s<&r10x=$p?>r1=LdL4Ey^YmHttLz&Q!0oY;r9y{-yZ*l9T=uDCOJ>7e%Gc0BlL( zNdbU%V(`E{8$tuD1#(6x8V6?jGEm^K5ecvzIQn3sjSzreKi4MNox95=0^8c6Ye`RE zcFUILss#~=%0QV077tnm_@u`cE-kvz($U9iV!2us5j^Wn#8}kF!s*HD_#MqjpAzJv z8}^2Qe#h?tz%IJ}zPIfOJHv|x!qhi#L~^!0Fas0rU`AnST~HDBiGZ+^NIq&)wsi74 zVKDayq!<9L_I)q1+HASP8idap+dvCqOjGNDLyJcvhsusT%l^a+af!9xRq1hE5bSi) z`RD;XkmrH|4(1l`OSL=YX?dI>IE5YCK((ZKX#vC?i`=2`9C`aEQl_J(*k1)Y*4~d? zrNb|05ezHV0+4od4CHsXcP}HhP>O(umG>qqdeAH0eb4#Q%E>=vXbReCARMaLdxj+{}O5d77!t|-vFjx+O?J8D;G z7hk3)hcxu`u_GQEL3@3L&8A{~R7@6haE6-=HGdPRT*bI<{i`uM+K3e6z7_S{R7Xfg z%!7yH$J&vd8oN6@yMmvaLR2J(5jUB->Atn&yZgsCn>1#?x9b*;souyBpQ1CFY8&9( zFQpIoRT;76W7_jU(?W!*dvnxoz+Q2JfjXQ}Bn7OgXxz*;?}2FnD{-d(8j%aE=Ki3O zZF40C51IOwG&f@KtnlPuD{)dHgEBa@{+Paewve6l8kR*9L!^r*(^&*Gt{IoT{d&1C zq7`kwc8d#Wd_ECndq~)sQPb%VM0kD0U^8*8_ zx8*ACt#+m$vwsIG@l|VWboYE>9gk>6!Tq>E@z02mBrmpwMtXQk4q`x>MSU)6XVJke zp3$|#fn|wN=x{f)nlCt;N`rO{7uhTYc__(LN6_rkQhS#}78yUnq zQaEGW1IO|EsrAJ_=TQGwA9=gc`2xd834Vd${|JWD_Ci=;0ze))gdwsi2JE2Ij)+)B zeXXOI$O|Fzf4=RCPB95Dp6=S?LLqedSKt7HF(%I+Z1zV*9YAMBMfaZB@rdS)|J_OI zP7i!%VM47FaNs_M==fv$U_!wl{_HI@=q)DADG-?P;X8!o03smf1c2V~SLld%+|1I% zT-nfR2iP1$vfmPmaoX)fgUs9@EU=>Ga@q|&^nf9}gCTMb-zw+|j}VxF)=n28eTH)} z-Ga`l<0eZv*2pNJG3CQ4NBI}EnlN)uJm@JHf_CpIyS2MZc5KcSz$J!Zs7%UirbB)J zxK~gCO%H!^Wo{h_bkvF!CV#d19rRsK$undQI8_36>r^?u#Yjy^1rC?KnM*UW+u@RcyeP9gS&UOSDU|`M(I?6p41EYrTG7Nk@8@UhV zKfS6oYi~XJRUzPp$PRTT=%+l_n)b-0r@1mAc#P z7s)nt{Ul&#wNO}E5lvRXsudM#7fC+#o<(vnXI;-UT1I09kR2geWGqkaN4YZva;PFb z-?r|8@`5?$p5;&&X4onAr4f4*tiKhzMcJp6N}pbxP+U~$^lIIMrpL09NIPOfi6pl7 zvS6Sxs!A0v4wnV=HAR-XMMz-098~UH@!KLvMslJ`_UFskB7S*&Yi#e*3_@+DB>q74 zCQ9$!?knxeELvaF5Y?@uMi4T$ruqkYQwysy4jH60Ri3UVy^mKYZ~xSJyZ+^Cz31mD zpk+6h^GmB^JExt&0oP;IA%`ND2DmB$?KXSCRoWo8l=w@t|C07o269xQXC-PLjB1{Y zkgso6=Y{({mz3oqjFBQr>KUcBpZ zNllVc$x_9}Z+y~k$n|OwY5Y|_EiVql#O$>u{xF2MGWDKY6C=j*{Cy>Wz3Nhx=PS}` zH%GX^oE*>3U8dTx<#!ECjMd(Z3~Q6u43EX}%9OQT@_D#)=Feg!XkIen*!3SP867S7 d9CQ7kp~QMM`RjAYe+tNFjpsU#!j+7J{{ajfuqFTi literal 0 HcmV?d00001 diff --git a/test/constraint/at_midpoint/line_pt_normal.slvs b/test/constraint/at_midpoint/line_pt_normal.slvs new file mode 100644 index 0000000..483bc76 --- /dev/null +++ b/test/constraint/at_midpoint/line_pt_normal.slvs @@ -0,0 +1,313 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=70 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/at_midpoint/line_pt_normal_v20.slvs b/test/constraint/at_midpoint/line_pt_normal_v20.slvs new file mode 100644 index 0000000..76d63eb --- /dev/null +++ b/test/constraint/at_midpoint/line_pt_normal_v20.slvs @@ -0,0 +1,311 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=70 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/at_midpoint/line_pt_normal_v22.slvs b/test/constraint/at_midpoint/line_pt_normal_v22.slvs new file mode 100644 index 0000000..07761c1 --- /dev/null +++ b/test/constraint/at_midpoint/line_pt_normal_v22.slvs @@ -0,0 +1,313 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=70 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/at_midpoint/test.cpp b/test/constraint/at_midpoint/test.cpp new file mode 100644 index 0000000..3525df9 --- /dev/null +++ b/test/constraint/at_midpoint/test.cpp @@ -0,0 +1,65 @@ +#include "harness.h" + +TEST_CASE(line_pt_normal_roundtrip) { + CHECK_LOAD("line_pt_normal.slvs"); + CHECK_RENDER("line_pt_normal.png"); + CHECK_SAVE("line_pt_normal.slvs"); +} + +TEST_CASE(line_pt_normal_migrate_from_v20) { + CHECK_LOAD("line_pt_normal_v20.slvs"); + CHECK_SAVE("line_pt_normal.slvs"); +} + +TEST_CASE(line_pt_normal_migrate_from_v22) { + CHECK_LOAD("line_pt_normal_v22.slvs"); + CHECK_SAVE("line_pt_normal.slvs"); +} + +TEST_CASE(line_pt_free_in_3d_roundtrip) { + CHECK_LOAD("line_pt_free_in_3d.slvs"); + CHECK_RENDER("line_pt_free_in_3d.png"); + CHECK_SAVE("line_pt_free_in_3d.slvs"); +} + +TEST_CASE(line_pt_free_in_3d_migrate_from_v20) { + CHECK_LOAD("line_pt_free_in_3d_v20.slvs"); + CHECK_SAVE("line_pt_free_in_3d.slvs"); +} + +TEST_CASE(line_pt_free_in_3d_migrate_from_v22) { + CHECK_LOAD("line_pt_free_in_3d_v22.slvs"); + CHECK_SAVE("line_pt_free_in_3d.slvs"); +} + +TEST_CASE(line_plane_normal_roundtrip) { + CHECK_LOAD("line_plane_normal.slvs"); + CHECK_RENDER("line_plane_normal.png"); + CHECK_SAVE("line_plane_normal.slvs"); +} + +TEST_CASE(line_plane_normal_migrate_from_v20) { + CHECK_LOAD("line_plane_normal_v20.slvs"); + CHECK_SAVE("line_plane_normal.slvs"); +} + +TEST_CASE(line_plane_normal_migrate_from_v22) { + CHECK_LOAD("line_plane_normal_v22.slvs"); + CHECK_SAVE("line_plane_normal.slvs"); +} + +TEST_CASE(line_plane_free_in_3d_roundtrip) { + CHECK_LOAD("line_plane_free_in_3d.slvs"); + CHECK_RENDER("line_plane_free_in_3d.png"); + CHECK_SAVE("line_plane_free_in_3d.slvs"); +} + +TEST_CASE(line_plane_free_in_3d_migrate_from_v20) { + CHECK_LOAD("line_plane_free_in_3d_v20.slvs"); + CHECK_SAVE("line_plane_free_in_3d.slvs"); +} + +TEST_CASE(line_plane_free_in_3d_migrate_from_v22) { + CHECK_LOAD("line_plane_free_in_3d_v22.slvs"); + CHECK_SAVE("line_plane_free_in_3d.slvs"); +} diff --git a/test/constraint/comment/normal.png b/test/constraint/comment/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..5a7036c29f62f9621384c02232d7093602630908 GIT binary patch literal 4314 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 Oz{YiUR@^nLi2nmA5tzpS literal 0 HcmV?d00001 diff --git a/test/constraint/cubic_line_tangent/free_in_3d.slvs b/test/constraint/cubic_line_tangent/free_in_3d.slvs new file mode 100644 index 0000000..fe3e29a --- /dev/null +++ b/test/constraint/cubic_line_tangent/free_in_3d.slvs @@ -0,0 +1,397 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00040016 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040017 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040018 +AddParam + +Param.h.v.=00040019 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=0004001a +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=0004001b +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00050013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00050015 +AddParam + +Param.h.v.=40000002 +Param.val=-1.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=300 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=12000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.point[3].v=00040004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040004 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00050001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=124 +Constraint.group.v=00000002 +Constraint.valP.v=40000002 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/cubic_line_tangent/free_in_3d_v20.slvs b/test/constraint/cubic_line_tangent/free_in_3d_v20.slvs new file mode 100644 index 0000000..2717b89 --- /dev/null +++ b/test/constraint/cubic_line_tangent/free_in_3d_v20.slvs @@ -0,0 +1,392 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00040016 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040017 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040018 +AddParam + +Param.h.v.=00040019 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=0004001a +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=0004001b +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00050013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00050015 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=300 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=12000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.point[3].v=00040004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040004 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00050001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=124 +Constraint.group.v=00000002 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/cubic_line_tangent/free_in_3d_v22.slvs b/test/constraint/cubic_line_tangent/free_in_3d_v22.slvs new file mode 100644 index 0000000..d5ca7c6 --- /dev/null +++ b/test/constraint/cubic_line_tangent/free_in_3d_v22.slvs @@ -0,0 +1,392 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00040016 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040017 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040018 +AddParam + +Param.h.v.=00040019 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=0004001a +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=0004001b +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00050013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00050015 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=300 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=12000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.point[3].v=00040004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040004 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00050001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=124 +Constraint.group.v=00000002 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/cubic_line_tangent/normal.png b/test/constraint/cubic_line_tangent/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..394f8ff73f425202b8707b3862be09473e65dcd0 GIT binary patch literal 5220 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 literal 0 HcmV?d00001 diff --git a/test/constraint/cubic_line_tangent/normal.slvs b/test/constraint/cubic_line_tangent/normal.slvs new file mode 100644 index 0000000..f8a534c --- /dev/null +++ b/test/constraint/cubic_line_tangent/normal.slvs @@ -0,0 +1,446 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040013 +AddParam + +Param.h.v.=00040014 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040016 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040017 +AddParam + +Param.h.v.=00040019 +AddParam + +Param.h.v.=0004001a +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +AddParam + +Param.h.v.=00060013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=300 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=12000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.point[3].v=00040004 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040004 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040004 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=124 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040001 +Constraint.ptB.v=00060001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000004 +Constraint.type=124 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/cubic_line_tangent/normal_v20.slvs b/test/constraint/cubic_line_tangent/normal_v20.slvs new file mode 100644 index 0000000..c141192 --- /dev/null +++ b/test/constraint/cubic_line_tangent/normal_v20.slvs @@ -0,0 +1,446 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040013 +AddParam + +Param.h.v.=00040014 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040016 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040017 +AddParam + +Param.h.v.=00040019 +AddParam + +Param.h.v.=0004001a +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +AddParam + +Param.h.v.=00060013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=300 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=12000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.point[3].v=00040004 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040004 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040004 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=124 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040001 +Constraint.ptB.v=00060001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000004 +Constraint.type=124 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/cubic_line_tangent/normal_v22.slvs b/test/constraint/cubic_line_tangent/normal_v22.slvs new file mode 100644 index 0000000..049bd73 --- /dev/null +++ b/test/constraint/cubic_line_tangent/normal_v22.slvs @@ -0,0 +1,446 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040013 +AddParam + +Param.h.v.=00040014 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040016 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040017 +AddParam + +Param.h.v.=00040019 +AddParam + +Param.h.v.=0004001a +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +AddParam + +Param.h.v.=00060013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=300 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=12000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.point[3].v=00040004 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.y=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040004 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040004 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=124 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=1 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040001 +Constraint.ptB.v=00060001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000004 +Constraint.type=124 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/cubic_line_tangent/test.cpp b/test/constraint/cubic_line_tangent/test.cpp new file mode 100644 index 0000000..4dcc334 --- /dev/null +++ b/test/constraint/cubic_line_tangent/test.cpp @@ -0,0 +1,33 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(free_in_3d_roundtrip) { + CHECK_LOAD("free_in_3d.slvs"); + CHECK_RENDER("free_in_3d.png"); + CHECK_SAVE("free_in_3d.slvs"); +} + +TEST_CASE(free_in_3d_migrate_from_v20) { + CHECK_LOAD("free_in_3d_v20.slvs"); + CHECK_SAVE("free_in_3d.slvs"); +} + +TEST_CASE(free_in_3d_migrate_from_v22) { + CHECK_LOAD("free_in_3d_v22.slvs"); + CHECK_SAVE("free_in_3d.slvs"); +} diff --git a/test/constraint/curve_curve_tangent/arc_arc.png b/test/constraint/curve_curve_tangent/arc_arc.png new file mode 100644 index 0000000000000000000000000000000000000000..c83e0a8890f47f4c2cc4ae968c82f58c611fb0bd GIT binary patch literal 5027 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 MYrB&F z1&R^~Xh4wV1+`dF!G(ZqiJ}5xAPN#82JQ<+E%x^N?m6GN=lkcLlbkup%=>$1p5HUi zJikfY>h7ehxIhsApzOTaaXSD=(##hl5C2oQFQXd(Egfe^hn*4Ug*|+B7ccYoCpTlW zFa^oZh@w7nPLEkgZolWk48 za2$=)Gl}d`o3ki4p=AFH0Mx#T`(j~}mMv{gfRp%F9NN%nr*|P(4(DW`P$?Tt_WheE z4K$VS#3Nd>F0?DpL9*i!+53_z5>OW}nKL~sArC)FVW2=3<}c`ze5lr5IG}R|E6dmK zyRLlmZ{~)@B)Gg)2er%of(M(+oiqSo(|-Z^zPLJ@~5MQ9bh(pyXO#SZL4y{|B`@3SY_rotxDq_RMu|JD#M1Hk3A`chd0X zeMzFZ4*wCkYy4p9YFOV~{wP6IRBLTXaePn}lxV7R8mkQ@?myQ0dbNsV?fV@E8b~N@ zqQv@Qj-Fj%nb8$00jU&0D^iZCa~mE}9>Uo>wZ~TDBJ>eZ9?^X#Zkjn15K;xK{r5YiAJ=UvpedXZy$wle|Mq zNKnY8405*B&A2)YfvWS1uhW*j4)!Q*i}-La4c&>dhRrXLL$R>v-TNxkS){##(6L67 zkVi8#$L@LKo`Q=s;;@2i{V94YZpJAm?Y=`PK)5rmz2W-$(zSGTW9-xFrx?;w@yScy zW*$T~Pvbd>SnbyQ!4$DI|K$QWm@yno$(Jd4K&A;Vi<^L+MFWyPqUKpx&kSD@!SO{Z z<%OkT{bEqsoT%LCi3y4Q^y^Z5y?n=tSoGI=z1-i2bUpUPw;h4U8U zx`;$&3_tw!7RxX#j2)v8dXp@xrQY zo6GmUvutX~+AP(HnWFlhKZ?e$bUUpnz=!DWeyXT*0*X_6SRCZa&bc0jl|0oYU~J}l zF+~h<>gf(dq#;LujpzBg7akdzv;!j>sSTR7VF$1#f@~}yA~syn)DLaI5#*a?WB5_= zKVG!y-HMPb=Hx+x0eX?i>@fR|C~G=v|D|iL*X`1U3&x*@DF4=HVeMKuzV_VO@W6h3 zxzguV2~x|3xa_qjiu+=6uC{gJ9S0xb$fD%lvtAGn(<*I@#0LA`Q2j6`v+iPmHHaI_H{&Gn91$SrE=q~o z?^>E?59eC$sp8}!4xr!J)%CLnQi)nfP~K1vZ=;imCIhD-WXD36qpfRV(VP`&@;}GG z{Z2P<$v>NMUQ`9P1JJTJ4z2b^q7YL*h)ezl6%ijD#5WvDZPoA;EuKB54F+fulPeV( zo)-GCXc5Z0(Cew#Ok0)PB)WfjGW~g9SM8M(v_SrNP(58Tn%m{W ztQ#3gc{RGRZfc^V?ma(d>fNDSn888y6TH4i-cXUSYienic|_1}%Efy1I!N2l=-~%e z(>M}aIHg+*4`OCg7*qZIBlQD=p~OQ;k=HdALdV}@E}r(4>kB<)3u)?^Jq%g<*oV|` z9LG+>5~%!>=`AmKZHGHYgi$2+Q0G)-m9AWkW-MBbZpS^ma9f1Oy#^#k!;?rmt4SC! zK*@)ZGy;WXSIIF|Qw76={0+*Heu-)Z&&Dna#qK3Cy0;cp4^a{GmUCAw<>dL!1UTG_ zYt4j^KdO5|u`-*4DDZ?V;rA>mVadVpIwTC@AO|1n-LS_ci0uSrWPip7)5KCZx5q&N z)(SYU?o~;)V=h#L9WCbJiKj`xNHy?0{gG4r-$|+Dlr6>LdG6MJKWmBt+&h% zg%~#?vt6m;E*X?h`G#=t)BUz`pKQWMACAa>B^k5n%%KZve|<_TfT0AhB8zuWA)^d zo+hI4`dzS()|;R|r`6ZJT1pkWelpBP8-ST)u%`0)dudt~H+MMlNu5X@el|UO(}R5R zS9gwwaXJbwv{pJ|WHy_ij^tzq5IcSij(h&h2~fADVGy9PfY(W8sDUVM$tBe&`C)h+ zA=j%Xqn3u?Kh0Yo{;<4uK=}(;Q80$Gl?ddGtL4NRAG{~k zu-^~iJ!3t48S}n_H*lc9jv&xSDhX85u(77H$Q`$=7S5u5I$*=Vt@1OkcFR(0OIm`e zfIWjM#}z}_F`u-Nb^+i6lF;3-r4$+LAP7zsabMt&0>n^t=03!J*hWXQmT8Bagpd(G zeaQH`nNepTyJbYevb0-BGTd#M zrs$V2%PC^UGHnA6h$G;TY*?1orlK*QS7R5uOuGga5YggAO1#jz8Sz%DCX9a$6SVhMSl zBhyM)3PA0d?q3}H=c4NS*i|h7*W^LO5>45t)bN$1FvP7 z|Dx8Csr#Rc!X6m+%N4>(M{wgW-L0)94CMQk7>9&B(<%x1-4=!5AToKP2(HVEDF3pYhg9CAn@s!Yk`-@xb*8;ZH2-p zEalf zpz=|?d*{dqNI}adfJhZ)6yd;_GgbK0OzTD!CeO5`!&b_2dycGgrXYy*yDhYq^ID0;l^o#6lyJcVbxND2I?K~jJlj5?`_VgnPd!;_lr z{*#&8nG(0mieUet_FU7dX{XAwHb30%FQ1fPfYUgRN-=z9?PcjZi4c{Fde({1n z!XEhk7rs&BeM;7&>?w8+U*Ow3stRSpMS`BY6+iOzgYXD5AUH9`P~pAa>sQ*pPuGc; z-)GC5m%h`fBVQUbW*x($9}22JXbWnNF;Kkx&7HKBJwq$=N{;dJnT_Kqyw-JUOsu&Fr` z+@rE4MjO6OvZ*>z=}5}|CQ=@}+Ah?1!Zw(=MLOj2%FbU>BxNqxGYW=1viUK$U8%_{!t?>8a@=?A2v)5|l)7(>33-@0Sd%;(Qz#Ode_A^YUZ6Ko~ z+i|%HAnBm~XoF6Uugb?XfzQmTcu<$pPmIAxYttv=f(vl5-0_)#>aY7{OE#=IH5ndX$x ze2h|#or_p&T2eW!{p0eS1@gODnL2(;CpzGV)P2q zJ`*oF#tmKsWv5-o-prjAL3)14-h7N2Ae~8@ddw&28?$D$g#r4p-vSv&4sD$QG#?-) zLEF37-}*CA?Btkwbxf1=Y}Qk|=@InC4|)-F0a#!?8RbuFLS^45SGa3P$`db>-yYv} zfQzNc8Z9Iz)49?VwYM-ba^3-b-xg!<3|ZQk>A+0r_T++){=M?jB>8M`99dI(6K$TH=b7tT-_M%S%PXc<}8B0Rmyalw2G1bW=f-j&JE!m(()2{mlw7t{N4F)eY!<6sO}0>cS4zKL3$Y zH7+`%{X#uQVC;bexz^%j7dbSS)b0`J9vE6EgjN+`0?E@ zA{@>jn^njzU&ahGyj|IAe6wN9#qi@cFL<%nPN7NKs_6dnR!I9oMMXH;wor_`(WGAM z>fn7Z7T0;1GOg9n{h6DQ@mIoYJ#a;qEtJ8uV3<)dQ*oUIT`y8a@9nKMCKc;o2ObPX ztj@!NI#W959|i=_h)J@MPVZ!3|((J{e{duUaBH4=y32%pVp(OUA859AKMa27CG)RG2IWzybt&J!ViOPa8>Aq6S`qqP zu8)0N`V!vFM+2Xt-BkV2O+lE&dqT=&T6wz|ABWwh{6^&&uVrDn zaAq>JdXGN%``z6Q+{Q^x5O`iooChM(a7Aj%`s<$KVRT1wZ65ktSoAk=4Q2Bv7*7?w zBIE~kQn4=9%Ib{9=5@HDmG0k3=87({Cdrm_UQJzrTTU$J^lw@FwP)cqqpwXQR|BOtkuytF>flIq(?lLm8psCA?}0iKI_IY^eX{WC z(P;Sbz(5Q1K@--HdXGIc2MX>Ntk)Bd&<{OaElvnot$@r1?Ba1u3^FDPM9B4|WMEtsN>KJv3o*Ve}GDDwx>dogqJ((c`I+cvDp1W z9cPrP`ucHhJ9V==6Rw8_BRA)S*ES-Kn|4*%@fUd)NH`RXF%X&3 zXFu4oyCw3+1}Js+-tQ_1s-UaAnk-F*TECCTqsNm7ZY!iRMe zUXk5ifveHnSE|dpr>O%_-$o`K9@Kvn>6Wps>uMr%RRgir8>(OT~J1 zc0?dY(v(RiF~}P}qk)IPp`tHT113lb;+0R8_yZFNnWKlX?U!NT=JxltghWhBR1z#! zl+EWkSzJJa*KX|Jc1IsrWgi2qFko*uAT60hRgUq(Zsx6zz0N1b=6CNs`^hiojeMp# zpcN(Tr4T@XmF^!p)(<1ohGeg~IL{E^+$W7?fAW31w}l`3&yUUKw6cK|SZ|su-B?iK zKI{g-iMvx)>reZGII=A(G+7yN&Q5}QEa+FQ5BU2pdkIp^68HN-$Y~Yv8OH4YaQLK+ z``>^AXB5u&BESK&zsUewa1;%2-ds(nY_&e=D*i2%hDkUN`NcnNV8Oxcw7<=x6YMZ_ zr*Ce&ufj$BaqmmfLU-@h0-8Uspr^WQhk-l!+stP90j5vt)js+#xy`@*oC@I7SVKaC zGnrHyh~$HPpM_6+ulkb&%p(xD5LA5dcU;)5GFgbOi#`~-CTB+MEUS+g6xZwegXR=#IHkmK z0p2PPvS-&Sn1+!rZkV^AL4O0}-Z=M)5$RhoS5z*F#Bbr?R7j?I4T|TW3&U+Ww27Y8 zxHs=&iak}5!+W<9Fz09*VtdO^S_UCO-9ox{Q>!{)T<-T)MzG20{xGL(1RJI}b|cu; zY0y!g_v)5(#>=r<$b&c2ghXxjT1PjTtgaW>X^(X;VO9#2*(663LtuYIsO*C`(*T^i zifNc%!%XHx_e$c@@Jt zC4M6y?EZA zxXYL@k5LgN*btBeB4$0Kcn+7(%JNLkz~gU4$pY!^kv~QDE}7|*pOys!$y;tYk1Sr# zSkC?)wfg%3$G9%R-AmF=dA-E5%kCQnMzl*VlD8A@- literal 0 HcmV?d00001 diff --git a/test/constraint/diameter/normal.slvs b/test/constraint/diameter/normal.slvs new file mode 100644 index 0000000..583b3ac --- /dev/null +++ b/test/constraint/diameter/normal.slvs @@ -0,0 +1,296 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=90 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=10.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=5.00000000000000000000 +Constraint.disp.offset.y=5.00000000000000000000 +AddConstraint + diff --git a/test/constraint/diameter/normal_v20.slvs b/test/constraint/diameter/normal_v20.slvs new file mode 100644 index 0000000..7427519 --- /dev/null +++ b/test/constraint/diameter/normal_v20.slvs @@ -0,0 +1,294 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=90 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=10.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=5.00000000000000000000 +Constraint.disp.offset.y=5.00000000000000000000 +AddConstraint + diff --git a/test/constraint/diameter/normal_v22.slvs b/test/constraint/diameter/normal_v22.slvs new file mode 100644 index 0000000..81a24e2 --- /dev/null +++ b/test/constraint/diameter/normal_v22.slvs @@ -0,0 +1,296 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=90 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=10.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=5.00000000000000000000 +Constraint.disp.offset.y=5.00000000000000000000 +AddConstraint + diff --git a/test/constraint/diameter/reference.png b/test/constraint/diameter/reference.png new file mode 100644 index 0000000000000000000000000000000000000000..09a3f28ea3d4013f8a291d7f5269b3b5a9240f2d GIT binary patch literal 5101 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 literal 0 HcmV?d00001 diff --git a/test/constraint/diameter/reference.slvs b/test/constraint/diameter/reference.slvs new file mode 100644 index 0000000..b403a72 --- /dev/null +++ b/test/constraint/diameter/reference.slvs @@ -0,0 +1,296 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=90 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=10.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.x=5.00000000000000000000 +Constraint.disp.offset.y=5.00000000000000000000 +AddConstraint + diff --git a/test/constraint/diameter/reference_v20.slvs b/test/constraint/diameter/reference_v20.slvs new file mode 100644 index 0000000..eb80eb9 --- /dev/null +++ b/test/constraint/diameter/reference_v20.slvs @@ -0,0 +1,296 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=90 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=10.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.x=5.00000000000000000000 +Constraint.disp.offset.y=5.00000000000000000000 +AddConstraint + diff --git a/test/constraint/diameter/reference_v22.slvs b/test/constraint/diameter/reference_v22.slvs new file mode 100644 index 0000000..4f8455b --- /dev/null +++ b/test/constraint/diameter/reference_v22.slvs @@ -0,0 +1,296 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=90 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=10.00000000000000000000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.x=5.00000000000000000000 +Constraint.disp.offset.y=5.00000000000000000000 +AddConstraint + diff --git a/test/constraint/diameter/test.cpp b/test/constraint/diameter/test.cpp new file mode 100644 index 0000000..7e2a06e --- /dev/null +++ b/test/constraint/diameter/test.cpp @@ -0,0 +1,33 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(reference_roundtrip) { + CHECK_LOAD("reference.slvs"); + CHECK_RENDER("reference.png"); + CHECK_SAVE("reference.slvs"); +} + +TEST_CASE(reference_migrate_from_v20) { + CHECK_LOAD("reference_v20.slvs"); + CHECK_SAVE("reference.slvs"); +} + +TEST_CASE(reference_migrate_from_v22) { + CHECK_LOAD("reference_v22.slvs"); + CHECK_SAVE("reference.slvs"); +} diff --git a/test/constraint/eq_len_pt_line_d/normal.png b/test/constraint/eq_len_pt_line_d/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..9daa892bd79c2e83b96f006078e05c37d84551fe GIT binary patch literal 4407 zcmeHLc~DdL8vO+l3bM%V0t#xyzDFQYt1Pm(fPjElH};T<0tGQ3J7G!C%3vu>A5Vi; zVSGGLB9W*s1c8vNBB(4{(Xa=25h(@;5+H>b@)86~#8BIr&h(ErlgypDx%cM#&UeoF zPLi>2uao+cl}i8s>MqU>`vFk!lD?IdBxkCFaytRgoOW^8aWMYUP*-mxhoyI>_f*5y zEC11=_nOD<(+#S0Hi$K7Gf#Wl8nV+=+i{OkbOb6tSc&89s$~91Z)}Q?cAng>3JAH( z7!&~5tqQ8Z*bNHe8n$!nBaXh1~q^ zsL4qqt2|K$bM7Tx*5+FlGh}t6F~}e;icM`hE;!-duTD~i{Z@6E1p`4u2)fv0AX7KB zFh$g42*6G^xeao>T88yNr^6f`)M{wc>(7A6v9x!%5E8cdZ~R0=Aj>ie9sz|cEQ35T z>>D!ka z%EQ0`=VD;!i$(l1G73Ci{C8%fKAUPkM0@L3 zL4rZpzryGs?;#JonLTOv7(g%Ak!K>1C5whf01K@-J1IfE18fcfEM@^zGV1*_fXEEI zR>WapGp7DYA$|o=tCgd%x4LVVev|rGOcOMp3BdYyHLhBIk3Njpp6(m?FnlHWMR~?% zrWC^bJ2ccm#OHH;HC!J!dqHL>^1ZY6W_Xt=*xek>)|_j4=3=u$v^bPPQ)p^?*yNpP zlp^77?#EaF%qBM22R^8R>wl6R+{T$X9ow7gH`$5{C|i=3VB7S78)=SNsu48nUErSI z+LsYR`w8+_xYoAoBDiLf%fP$Bc{~&h&Z{lFQ19YzrwV$yZ(mD9p!RH#|gSR z$nsYKU8>mK)fe)=YdN(p7aLjL_vFEkySMq_bx>E|JPa_B!u?FwqT0?fS4FxuRP?Qu zPNnu*bb@UIIGJvYeD1B6!=Ti%347u|q{*dTr3>*^5SJk{N$y(;cZI2BBgmn@IpHAXo zK*UM~RpXaVqO-^=hz-x8A4rA`7hMy7t3Of>*N0$kw|j1?AbUEe%Udx*eun55p1q^K z&1xbdlY`@k#@_U;-*p9%y$cIVf;A)Kk*lwrY9;7#p5@l^^hXvEr5}2#eEEV`v~aU@y^ly2U`^&0@uJa* zt6CBhzU@l@{V>ZYv_=lD2O|kFpSZd~Ls*B8hUxEJ+w%~XVX5>y*3hIIVtklM=9}vW zxi7b+jAnRPW^82#U$7ak0qGElvpCZtCtzHtY;|-wbDU{Y(BCVBM05TiR*{oQu2x`aG^n)v}fb_hySSwFbvqMif9(V4mygr6FU^aDunH z_1%h19nue*6IUEuXQu?>;WowBj zkC62W>9Ko%O1t=AUVvJpq>98&A@=gv-ceMltwu!ezXRw1WqSha{)@E7sQ6BZtSy0a z&FXDbvV%U*-0<$Oo|>0|1jY_n|Imudb^uL^La^#pp84t&hUFCb3L?36+2?JhodoB6 zcJ)WKiL4c%ZzWey*cF!q?aa#90i3@(1^an5hPWehg;z^etU`*}EjOFdB)%enywZw_ z+1+f`e2w(gdSxtfx=vZTB{+u6!O3G^FeYx&kNnK4Pa^G@I7d>l*N!Qr@E9ULyE1`Y zKO?)VqKSmLA*8sL8MX181W{c61iSiy6^Lv1xJ}`X+cU?zJ4X`)gZyiCe0IMd9!XDP z(>Ptio7pxV^s1B$QOX$aaWE^M%RRa+mS5FzybMn-;@6gwl!K$f_X|#LI+{32vk`;N zVia|CFGH8kt{T(IQw8zHfy~diCvQcte9irKvwXRgNou%(EGj)q+}8j0ny8vCz99l5 znoU|>ILBw+1p^qA;%lD}#&~XJetJPx>Pt>VUpnH%3(;Fe^gkoJPmQ+=k5nfKH+CNYJn@t|f;t6Fafgg7_=(hT;#<&csYu%U{uq&JWk_Z?2BCmY}BOY m_sUjg9m+SWKkqLCF$mt^KW6VyvsLnk2yk)S>u_TyHtm1O@CZ== literal 0 HcmV?d00001 diff --git a/test/constraint/eq_len_pt_line_d/normal.slvs b/test/constraint/eq_len_pt_line_d/normal.slvs new file mode 100644 index 0000000..779430c --- /dev/null +++ b/test/constraint/eq_len_pt_line_d/normal.slvs @@ -0,0 +1,364 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=52 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00060000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/eq_len_pt_line_d/normal_v20.slvs b/test/constraint/eq_len_pt_line_d/normal_v20.slvs new file mode 100644 index 0000000..6ce37fd --- /dev/null +++ b/test/constraint/eq_len_pt_line_d/normal_v20.slvs @@ -0,0 +1,362 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=52 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00060000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/eq_len_pt_line_d/normal_v22.slvs b/test/constraint/eq_len_pt_line_d/normal_v22.slvs new file mode 100644 index 0000000..78b22b0 --- /dev/null +++ b/test/constraint/eq_len_pt_line_d/normal_v22.slvs @@ -0,0 +1,364 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=52 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00060000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/eq_len_pt_line_d/test.cpp b/test/constraint/eq_len_pt_line_d/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/constraint/eq_len_pt_line_d/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/constraint/eq_pt_ln_distances/normal.png b/test/constraint/eq_pt_ln_distances/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..4d040037bd765eb2d1166acec12fd7730eb4b5ca GIT binary patch literal 4363 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 Sy9<5f0Nb5*IOc639QzYcFW(CQ literal 0 HcmV?d00001 diff --git a/test/constraint/eq_pt_ln_distances/normal.slvs b/test/constraint/eq_pt_ln_distances/normal.slvs new file mode 100644 index 0000000..6a196e3 --- /dev/null +++ b/test/constraint/eq_pt_ln_distances/normal.slvs @@ -0,0 +1,389 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=53 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00070000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/eq_pt_ln_distances/normal_v20.slvs b/test/constraint/eq_pt_ln_distances/normal_v20.slvs new file mode 100644 index 0000000..2d878b9 --- /dev/null +++ b/test/constraint/eq_pt_ln_distances/normal_v20.slvs @@ -0,0 +1,387 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=53 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00070000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/eq_pt_ln_distances/normal_v22.slvs b/test/constraint/eq_pt_ln_distances/normal_v22.slvs new file mode 100644 index 0000000..1e4968b --- /dev/null +++ b/test/constraint/eq_pt_ln_distances/normal_v22.slvs @@ -0,0 +1,389 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=53 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00070000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/eq_pt_ln_distances/test.cpp b/test/constraint/eq_pt_ln_distances/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/constraint/eq_pt_ln_distances/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/constraint/equal_angle/normal.png b/test/constraint/equal_angle/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..3f81a9543e93bd20eed1bc2f27cd82c6e4962e4a GIT binary patch literal 5267 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 F{1e^z4Lkq< literal 0 HcmV?d00001 diff --git a/test/constraint/equal_angle/normal.slvs b/test/constraint/equal_angle/normal.slvs new file mode 100644 index 0000000..b1d538f --- /dev/null +++ b/test/constraint/equal_angle/normal.slvs @@ -0,0 +1,463 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00070001 +Entity.point[1].v=00070002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00060002 +Constraint.ptB.v=00070001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=54 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.entityC.v=00070000 +Constraint.entityD.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/equal_angle/normal_v20.slvs b/test/constraint/equal_angle/normal_v20.slvs new file mode 100644 index 0000000..25b9634 --- /dev/null +++ b/test/constraint/equal_angle/normal_v20.slvs @@ -0,0 +1,461 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00070001 +Entity.point[1].v=00070002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00060002 +Constraint.ptB.v=00070001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=54 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.entityC.v=00070000 +Constraint.entityD.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/equal_angle/normal_v22.slvs b/test/constraint/equal_angle/normal_v22.slvs new file mode 100644 index 0000000..6fb46d0 --- /dev/null +++ b/test/constraint/equal_angle/normal_v22.slvs @@ -0,0 +1,463 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00070001 +Entity.point[1].v=00070002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040002 +Constraint.ptB.v=00050001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00060002 +Constraint.ptB.v=00070001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=54 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.entityC.v=00070000 +Constraint.entityD.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/equal_angle/other.png b/test/constraint/equal_angle/other.png new file mode 100644 index 0000000000000000000000000000000000000000..85bf8263a18ba334c57f0c7a04eb833f4cfbc687 GIT binary patch literal 4914 zcmeHLX;f3!7CxCs1Tmm(iWL(C#UeqXsO2$hK^(z~g(%alC{_aq7BEa95v@h0*QYY5 z3@y*8fJ88&Ou2w1T2clFK*m^5XaoTRgfPC#jaaEm+x~d}x>mB1oV(9H-`RVAXMZQr zI~?rQRX9;P@Om;4=PCMva&A zOmbPQ=HXtU@To#1>VHo zLa^!0oKQUc)v0TXW`&b~Q4oBUiU;tvo_9vL^7^$$df-;{>x2aH_Gi&R@|hXv;;ioY zl-EyZ`F|^2mDha{XB(XYTQz3(h33{MN3eBB2LSKA#CN5e5g(L8#lYKNS4Wl_-E{)M z|DAyjdDtBlzkd4u$S~r4v#1!xxgVZ?GYh%T!5sG=i5S3GU_J}q8@0y`$Yu?ZfS>M6 zLYeN+V<{2n@1KD#TT$rs7Ymsi;Nv<(;?{lO{^@MwTUot3ElGfz(pdH`BE)*RXW0Y) z1+OU~#a2061BSs2^kkR1#U`(x&i4PuG{t%~|7Z_lFTGW$2uG#s0TADr_bygK+Ryh` zV!7u@Go|eO5%cCKhP(OkE9h|XsTav_MDTqShqRlWEwRuVy9V`H$Q$qPDcVKG0!Tl2 zChl(?5gp71sU~Q#ohfC0A)NP!47N#~h5$Dz{PxQO)|NgW)Gn7rMcqcI`@Xt8`~aN3 z?Y1&UP@lNpfI4STG%oqP2P(5oqmU(G&xi?2`Oy5DkI0^>6$N54bvsmscgSZ?r&6o- z;4@;bVWA(1u65xj+;9qQmnM_Jh#~fvSYJVA?Vykw4{&z)9?Vf(SJjF|Ee7Fvl)06^ zV#dVe&?S6G2Q{fh@*2fw!&b7WHpCZKX}Jkn-$keLYsNENuxe)B1MDx68nCfPChGGu zoRkyId4$Dz=tRwjgv1;U`z$&)wtOBX@D94lMqPf!0@S$Q59LfCtQ=p;&q!7pZ-Le? zK|yP!z{hk^TZS2rKtLCVRIqAPF*?P2317lf4(cO=O8?$^deF zJO%&Ca1MP$2gPY>$Rp680XBd7lsL+;HbpAGZ{Gkn;u-P4a=v6SI+Ob|QV}&7%I~>I zLSGF}X)yiuP&eJqb&K;ImmN5f7IAx0R6(aUq=_8KiwKV1ig592YY7z0mu#%+i}?u` zoJ{LC;t|X_V~T>|<{IG_WL)7s4jkmC`I3=DGd>)-gFHFeWgR&pOSdiO&Eq+HL`snLqu-t?)l8fl$Pl9+tU0V*?0Gp0IvrWy;4ogz$DN8B2gOJ8lvzqPEykvpl_U zoWnqj%J?_W(c(3kwREa~oB|_Xz`T%#Rz<%RHpVG`y0ma@xhwgnUPPBKK%*w{>VZB# zr0_BbIe9dOsK>B|+^{hZuA`Q#@s!JZ(L8jL=nfTV=I_pDo(a1nPhnbUomF7G<}$DRv=rch9;hzvp|3^^(PXtXM90Rw z2;Zl9oy{G0mKP>(i?d3FVDqbpi}ElWwE)qcvelY6+@(y9FjyomYCKwpKI;)P*Q3^9B^$3}>! ze5Tyn=n}qG`2&~VDCChe1;)k4_1-}OZ9&qijz@o)Zpe-EjMTWM5;K^^`%LBpgp+#0%r(4@5PG7pfYWl6i!F5S%Y@ zORR6Y*eRw>T$}W*qD|#^nU!{!PY#G8LwbT(zD2#^!WNnKsfg02Ys33k!G@xv;gG9PrQvis)#@BMq^V>yd?=H9<%hPB!G06$g<9k7%(4^`9cdKo zqPF0i3}#*HY93**SK$knl?vc%A6`g;v7~$!fqhTOH$fQ7L9MZ*ef3)mmKty!E{)NdLj0RPX=9 z;h#ACOr&i2?Do_K5u4hH6mdrG_f~?eWTsnE>c}&s-4Rb(LLY z!XJOJ0c3c2sU&)8sDnTsF?`6k>2BeT{1Sl`5Fcnke;(TYRausPY9Gu)?sHy5XVwF| z`un95Yom45K&1%>60CFICm4>{H^&HQiL`l-ni}q&i*2O?Ehl$q`^}EPrA4~|Bhm2@ z&a_g1-`zI;t)^R3!4m=)^h$F*zI;vSQY@UU-&$6fSCC(<&}Cf}9GokNS8FLg}nJ5n%b1#Y;?mp3<(;ONtcMLVFwvQh=LeK2IR5H}! zIcCvQ;wRryJP96tQtX^AjWDoC$Iv`n6E2?=s!^%bD2r!BcFc==<#R#z&R(PPf@7U} z&7lQ%sx?K9sdByR&qm^{WdjQu;!-?mqNCxW$o7DW#Ni-Awb8Xz%|XEb<7v8!50%Qa=aQS+=B8m7a<7$*MEHn&paN~(vT-A~ zT_d-0Hq(lH&`S&fPcN&@Bv*r0MJ3Z7Cq*+PRP=(WE4!=P%)R%Io#_wez~Oh!e1G5X z=XFl{mqB*{)E=w#YvDw-P1w z%B)p-HQYfTzIonSpsdZDzpwwOr*iJM>%MTI$9SBMGHVGdj*InAzC=U2#a-Zu0XU>B zWS{`JVyD0RKatkJHGqr&`z_r83(GbIlV|e*xM?yKtl&aOU}67%TBO^#tXmjLtuHQ@ zw7r4-;(cLaQ?E=QQ5Vv5w9A4Sa9up<;H?E6N})0a*Redi$Z5A7^3SStz}Ng>ibPR_ zyDf}%la)MBIRk)I0po71g&^;k2^!Xnbb>E|7QQkEv$yYi-w{@lr((ee_5#9{6f(@A zo%))My35bw^>hRRP~IU!g;t%RoV2_p5KHbOY3~<^yt6&>hSwKqdty zRK7>_KKmzwohLf*Pt`1~r8|z@D$Qe9hj%7J{iBHQ_OiiVEN8xDHszEzMTIqFMYpP1 zUoE)ts}?!jEupJ`LtqVk^$1;ugsWuJ_tIUg^durHvMU){!ZrGHtg2O9DpD*1rRk&k zmdf5XqIScK^Nbvc%Dz@aJ!b|I-R#|2M{^WT0Ii48exJwj+bl7-qxs8rAgx_r zTo{(D_7P% zaw4F%PGZkK-bA@pD7~S6s-ZPKJT3|-KJD8-*Ohm{J1wQp5fStv%5omgo^6^DIg|E^ z8g2S8yCciez4^&}8VcQ0Xqq1yL(*4XS!>nlWdl||wL1T*{~xPZ_e=j~A)0J(kU#a% zv7H&p0~xoTIhAhyE!8FbC^V8)3}Q!VeJnG2ucsjR4o}jRq|IFwG!bfP~HP9K&(r zntvCb#)m3cG*-*TgZc_R&kHH_7ZAYt{xGCluv)W@$9oh*gER^je;nH&QAuIzRilL; zkU+-9zK>uqAnvaLhSw?VYY8*}xC$-u;uqTxz?NR6SIR`!-;t*68WnTy6Qfvkn7;LEPH*8;%V_C6xBV!0_OP4sW0w=v1Z>{fgcf@P^Tla-{q z1M*Uxz+&xG&^f1G7FRa_@uC@>`a8yC=qu+QG6?+;Z*L2dKe)C?vn=U+6M)`T3B5s7 z1Bm^NBO#>ba~Ua$K}iK%X(>Czle#U$5|dbSmf6TQ*B6{IQ@>)ZEVw)^;LJTFt1H9B z@H_PJAU^96k`-HNM+lK-dOFs)1Bb67$bnw5$!>=44>%n*J{5S&%i+4icCXcxLnY>+0yOdmh%A4i%pNo4+Wj3fvSbB ze6(J%L4_M1ea~>5%|}-lj)L!Udi=gSK5cb40xGTQ^Cg(4{#`3~;h@5CFLH=1d6g5v zK5fNf5HsnyO~KquQ{Z#^I%;V4vF{7C!_kXZl3bN3zpx%|hsGL?ARYC&UL*`&)V zk>|S;=NS~HTy{NWmt4|Tr&0)|a+0b~Of1O>(_DcYdPQ4_#FMC)b2{u@VOl?|8SoaQ zD3()vUv-7|WpkV#U?Mct+76vq>a#&0N=v8v^)>ypxhD~ZpC!IlCMDozXq*G(-^9}F zs@ih>74ZF|FYjyK^y_aZftKFV1UK}ybv zg#F@#fnwk1oGqHV-mNf*vASzfpqcgZeD$7UIYPa0r&z-+%Q=WYHJHhPvs|W@hG@EN zp)V_R&IJOAM_bvsNt)Brh@bV>wV25ml0}z`)+(ZU;-qD@@u&2`ZZWO`bCZ#}$Em$+ zaF%E<(W7g?-vdtBUMOccQnUBAUn*BG6=_6@d0qXeN4Z^FQhA!Z&O|dGAHk91x0u%G zgONugCB*$(m*z(S=u>s$EYVcA2uD~kWf_OyM{g{?ovkkP>+Vg5D=MWN!{f8G(5a-h zdye%HN|pcy&5MU9s2UTX$2FY^JoUeSEuforU_JxL0Zv51s4KD2%*sq5)chbBp5O;D3+9?qct^C^TVpVy0>oLtM}dWopZkL zzBEtw^(sp9l>h)L8#lOk1AxRxF9msc42-rw2#~6X0wk;d(oG$;R~EJ4vCRTukn=AS zZ^AOdB;&Z5pTw1wy|)OPfvxTobr=2|vEnI*(qG@Jx$xhGNg~eeMydB>9>%SmwSg5h zL-HvZfc{nSb2}|I9N9@nlH5K(QLc3c}@d>?8r`${lIgZ;{U!DvZ;{lZ!B z7WiYaW5xii+nw?`==+--e5(MY_2Eqe$mUCiK<~Gi$Zv0tzV)@)5o2-&v;lqTDxAWs zRF<&V{&WRE%F3IC(#Kp7S+}&Afex3M8>n?mn}In%KIDDmqgEGE}3##(*MYDm1q}D;oQglO^Y%+^bLv z4di?K$yMlO2j#Y~V)jyKE8MpB?hfSI7@_BBp{|{IN<|hIzDp$9xyv2dQT{=14zFH^ z9snJiq$F{pESESOSy0E=E(1)qP`RqRbW`PY15eLXhy@?-s-vvJf-0 zqb4_}N^gVaY(;^ZOCL;ungy>%m{6e~a}^P*yrr6KP%XiLh=o+HB0P32g8zaa`9TBo zYB4-U!eeUY(wsX;?lPou(KGLbpjH7D`;fDgC8{pfW%kSJ)I-A;ZjSM2Zb|uxh;(Jt=4BLHjQQoE) zbr>xy<0@+^fUXJ36i^D+>rwsg!+^J#YSeF2nOLaGjEhs#N$rYXI~f8^#2S$BRt+hn z1ZVTudD~0;((g1mH#jT85$5c$-tzca_O6>V#$?%%KrU~-qv;R&G%>HvhvAIF^X05= zNDbm5zKY}W#8w&FX^QdekIy%`3T_)wxxh);{IZ<7XY{SX9e2HwJpvd%^ZOVc-OhZW zeb8i#RUtqK2A$POsIUClJ~+j+h?kf6}y|54RUQv2H*vu@zg#NJ59kcw+>w zq+z$0QP8y0m}0ET9$YkTd@YX6Xg}^Q^^!jSaH8G18Mm9RdOo@s*LD{K;M|veR}o)j zzwjXIgZ3}j4Q!M7yen&EK>f)XFZmY3hQr9oZdCdgW=(P0+inN*6m{C5>oe01nERG{VqbNJWWVzA4i&I3=8$tG&)|3%-3HgN-}iRzukRUmII_E^w*GL-jnEyP3^6%GBF8(-W)Af4QJDLCVpW@sM4?iR zL7WH=6j;+mM4=aYn-?x|Qm_bF*ot$*G@MdE6&vD{w&J#$E%Aw_!4rjrvBvyW)?P5pD8vp?O_JG@>D)l z-7lnA%8hia-zHVFZ>{YgMfE99M7snkgWIDyFv4Hef-$ed% zR)u}_=@-3=RQane#V4wQDEWi(tXPY_O&A38vSP)-Wi%v7cW!qG8ZyW$)dr&*A%hdr z(G4<4myWn_B3t6=bYtG1N@D(3X(jL(_K4uXj#G>manUcX9 z-q=ft=H-k`5Y>xGrnOO;S4g53+Bu7D1lnNIp$+K8DiDbV!;&ps^Gl zvo52={P89JB~95@U5OQP{HIko{l3Pcsw!Yb=9-p4VW&8P(EY>EjzSsgd+>?jSaenEwxe`ek7Od>@HrQ30AsN6(nsJEN%E$M|Y!UlBo#6*hcqVqj?_r*C5O zeBgejhgtXBY!TnEmrl8cuZ;xz~~&H4y_l+sFLl z3rZa0Uy=!ik@Uy}lfWbpKDVgcBPK|x`;uBd;Mz`$UpPza hAD?CaSKl?Jl(QvI8Cd(5E8(v-V56(MOXV6U<)4seU~d2b literal 0 HcmV?d00001 diff --git a/test/constraint/equal_line_arc_len/normal.slvs b/test/constraint/equal_line_arc_len/normal.slvs new file mode 100644 index 0000000..1f7869e --- /dev/null +++ b/test/constraint/equal_line_arc_len/normal.slvs @@ -0,0 +1,367 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-4.99993226121819223096 +AddParam + +Param.h.v.=00040011 +Param.val=10.00006773878180865722 +AddParam + +Param.h.v.=00040013 +Param.val=-9.99975209012342425297 +AddParam + +Param.h.v.=00040014 +Param.val=9.99968435134161737210 +AddParam + +Param.h.v.=00040016 +Param.val=-5.00031564865838440426 +AddParam + +Param.h.v.=00040017 +Param.val=5.00024790987657663521 +AddParam + +Param.h.v.=00050010 +Param.val=4.99990506249655908277 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=12.85283693258537773829 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=500 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=14000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-4.99993226121819223096 +Entity.actPoint.y=10.00006773878180865722 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-9.99975209012342425297 +Entity.actPoint.y=9.99968435134161737210 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00031564865838440426 +Entity.actPoint.y=5.00024790987657663521 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=4.99990506249655908277 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=12.85283693258537773829 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=55 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/equal_line_arc_len/normal_v20.slvs b/test/constraint/equal_line_arc_len/normal_v20.slvs new file mode 100644 index 0000000..9f4b27b --- /dev/null +++ b/test/constraint/equal_line_arc_len/normal_v20.slvs @@ -0,0 +1,367 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-4.99993226121819220000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00006773878180900000 +AddParam + +Param.h.v.=00040013 +Param.val=-9.99975209012342430000 +AddParam + +Param.h.v.=00040014 +Param.val=9.99968435134161740000 +AddParam + +Param.h.v.=00040016 +Param.val=-5.00031564865838440000 +AddParam + +Param.h.v.=00040017 +Param.val=5.00024790987657660000 +AddParam + +Param.h.v.=00050010 +Param.val=4.99990506249655910000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=12.85283693258537800000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=500 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=14000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-4.99993226121819220000 +Entity.actPoint.y=10.00006773878180900000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-9.99975209012342430000 +Entity.actPoint.y=9.99968435134161740000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00031564865838440000 +Entity.actPoint.y=5.00024790987657660000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=4.99990506249655910000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=12.85283693258537800000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=55 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/equal_line_arc_len/normal_v22.slvs b/test/constraint/equal_line_arc_len/normal_v22.slvs new file mode 100644 index 0000000..733864b --- /dev/null +++ b/test/constraint/equal_line_arc_len/normal_v22.slvs @@ -0,0 +1,367 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-4.99993226121819220000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00006773878180900000 +AddParam + +Param.h.v.=00040013 +Param.val=-9.99975209012342430000 +AddParam + +Param.h.v.=00040014 +Param.val=9.99968435134161740000 +AddParam + +Param.h.v.=00040016 +Param.val=-5.00031564865838440000 +AddParam + +Param.h.v.=00040017 +Param.val=5.00024790987657660000 +AddParam + +Param.h.v.=00050010 +Param.val=4.99990506249655910000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=12.85283693258537800000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=500 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=14000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-4.99993226121819220000 +Entity.actPoint.y=10.00006773878180900000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-9.99975209012342430000 +Entity.actPoint.y=9.99968435134161740000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00031564865838440000 +Entity.actPoint.y=5.00024790987657660000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=4.99990506249655910000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=12.85283693258537800000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=55 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/equal_line_arc_len/pi.png b/test/constraint/equal_line_arc_len/pi.png new file mode 100644 index 0000000000000000000000000000000000000000..4e06218a09172def33473526f2b172fe806363ae GIT binary patch literal 4667 zcmeHLX;f3!7CyNofJ6pCCK;kwp_GWIb*M5AA|h6)VnB>nWs;yEh{}8uQHw>8Vj08% z^$Aj@t4NtjNE|AP2vif0xo8P8$PhxF#DsT);F8rx>-*6kZ>_GCtaI17``ml>xA*?` zcW&I)Eslx`3lsnVicTBtT>*e7(!abcHpBEuYXv|Rai1T16ZfAARw=Xy>l-_1PeFAI1oGMZ!(HLuNQWC*RGT*$@mX_ zbuql-V}TPNexzXI7u{vurK>aYJ8Hx3`4k;&JM&b>bRP6q`{{9PxChY_)Gw`>>CQJI z!O9Q-(|X`laxZO{f> zE9C)_lr}506wDGPfU(XQjKdRl)1)%r#K_s zny`G_z6=8JXNI%o91{H`M4$lN=1m*qnWFLIIK(D>3Lx0iVds;X!Jgsk#|d7a=$%8w z)n{DepZXAHsd0dR{_Y<&allMfZMJN;w(2emCxBdO&7dR>5gZ-iB`1m{w|0Pwu5I|5 znF3;K&k#&ZYAXsR)qias* zzwsKn^_9jPAN>bl>js!@V}DHnu~VT0fyTp2^1!$ent2X|w31m2=&k}B01++wO$uT; zIv>T=^8Ry$PIGg=`Igq6xQpM-(dd7�KG~d(fh9YWLe?bIH!JsM=vd5EUXGU048~ zy1+JRrI0mjbFLK1Ttz1=?@~aPK}6_2=kzq#LoV3dE-5yXcxxlC)qyY#W@p@22Zg#U9#uem@ftrndE$n?Wiie&vvvaM#hNrKZYZ9`P~2PyD0VP=12+G&0SYg9W&*gX z6qJrVk0wv1ut$3re&makbJ&|ql_4~R#nANB$HTcf#od9*$M4qZ;b9XsGR4Ldhn{>N zyrEc8Avk{eqexPNPKfB!E=-ZwbiRmhQi1LcVWQu^`Hvn(Pc(f2wme#5JMG96^@S*F zWFL8Z@PT;8MC(vle(~#$2X!^$En1Of6Ja&2%^j8kHV+!opg%CqkGoo!(OMB!-*fOY zi}C@O+WJZfU70G619vy0@b;|jz=Nk3$&2nDU3N=WfgTLR1NLlJbId=q212#}1V zC6>j#5OevSOq)f(e8kR-%`l2p2ZJgUlp#VvlkNLlKhE~H1wX02uLQSX5*Ih^E6S*e z1p$jtSmqlt;NB#XuSWxTt04vb9-Gfwg2Iba=it`49(jPlE1N|Dd5h3aMQk)*jNQVP zQ0GyG46#uk8&4G)r&L3o`gB5IaE=P%h}VcrHr4Lwx$w- zl(3B!_1#=i%XnGwuDi~-!wBt^$vP1H$ZYQvCo`1G6UbKyW~Ouk$>##Wj435W-mA?; z;1`S+Paev4w9*M$SsL3`0dKqmSdu^wqX~he&mIY zkQ>sf8z?Hm*6C9eg0QkudeeSl6G4-8YYw@m`g-==j*30B@kXRUP=&eKWjYZuEK`%v zePzG7t#@^ZwIdenOXKCsB@MFHdTM0E_F0wT8eP(p-%Dz=CN_p0OMLi^>z{Pe$+JLXX08l4g_^VEXwT0S{ z7gup$%!@%#8F3Uq$UPMbSY*{wSgq<2v~ZG~t?TQ?Caw}Lrm~G zLgjv5jAW-5`>i}VlQhvYSdmz`Oj}{;T4g$62$$s^Wcm%(F@s+XwsWe;Z^c#3+2WQe z@nF?NOH`dRt)~2?_}1^M=8MX@Eh2>$yJ{o@6J*h{wQHWsx;YxzWmXF+-D=*MlrrB* zhB=WnjibH1XLZoEGvO~EK1`GkvW@w2FTvLU#afKmrP3uolhVLhK_}?82)tQO4=pt- zX7I6E+neQvJp2;i<4iJ(9}gPfwc?!_EKMoS-KvZJTrs(6Quv-n{QzPy)vPi}h8-Uf zz+a25*~0|l?@tSpKb2^q^i_47ss7rrF*TpeO~t` zOVq<8ViGz59eDOz!CRBkeuwQ4bx{+gW3cq~ywPWQmE z*37r?wubf6a@4`76RdYmI=aAm@zOESpV>08mC4lVQUK$j?L1g-)M=!|N?)!txZbJ+ zlswg;$Vd=B(5Z!8<;P*&T9c=g$HkCUF+x4m+=2CZ*0{lKfT++z^Lq}D0~RGgol>Iq zFGsZ-zR6j|B8q6Za!KOE8>TDmX5_Nmsss@9JB41%yR|xN_GpUcW zl<7;4FzSp*^6pCFEwciC`2t-^Ny8nV1)(}S7+}1Mv_aY_&hCGQ@>;f*3vW3tzcaR? zBlKL<$giF2w;chzM6foAImj-*Ox6`75gU^Peo>8u zza{m`M;v)K$mB%~2sx76F&UPz?qXEm;n!`~59GEW;Tj%^)DVFPZ5$Rx?xr!_;rw3g z!3Coc%G^rPK1&Plrf~BZCv4TI4ka;i#cirX+j`oIk@MlykT?|tdRS^-$|qDMVKk{6UTj(8X-__FCEo9XC z^m*wFs_y;a%xi5Bw6QohBK literal 0 HcmV?d00001 diff --git a/test/constraint/equal_line_arc_len/pi.slvs b/test/constraint/equal_line_arc_len/pi.slvs new file mode 100644 index 0000000..009148c --- /dev/null +++ b/test/constraint/equal_line_arc_len/pi.slvs @@ -0,0 +1,367 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-4.99999651845403381145 +AddParam + +Param.h.v.=00040011 +Param.val=9.99999682365714548382 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000349810587607635 +AddParam + +Param.h.v.=00040014 +Param.val=14.99999363650495176614 +AddParam + +Param.h.v.=00040016 +Param.val=-5.00000000434181757214 +AddParam + +Param.h.v.=00040017 +Param.val=5.00000001080568345913 +AddParam + +Param.h.v.=00050010 +Param.val=4.99998746596540222242 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=20.70793025565613021399 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=500 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=14000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-4.99999651845403381145 +Entity.actPoint.y=9.99999682365714548382 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000349810587607635 +Entity.actPoint.y=14.99999363650495176614 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000434181757214 +Entity.actPoint.y=5.00000001080568345913 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=4.99998746596540222242 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=20.70793025565613021399 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=55 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/equal_line_arc_len/tau.png b/test/constraint/equal_line_arc_len/tau.png new file mode 100644 index 0000000000000000000000000000000000000000..1abd363cd474e157522b2b20b5ebef35ccdd5194 GIT binary patch literal 4963 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 GM*I_>3NxJm literal 0 HcmV?d00001 diff --git a/test/constraint/equal_radius/normal.slvs b/test/constraint/equal_radius/normal.slvs new file mode 100644 index 0000000..978b12c --- /dev/null +++ b/test/constraint/equal_radius/normal.slvs @@ -0,0 +1,349 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.normal.v=00050020 +Entity.distance.v=00050040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=130 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/equal_radius/normal_v20.slvs b/test/constraint/equal_radius/normal_v20.slvs new file mode 100644 index 0000000..cd891bf --- /dev/null +++ b/test/constraint/equal_radius/normal_v20.slvs @@ -0,0 +1,347 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.normal.v=00050020 +Entity.distance.v=00050040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=130 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/equal_radius/normal_v22.slvs b/test/constraint/equal_radius/normal_v22.slvs new file mode 100644 index 0000000..57b6d9b --- /dev/null +++ b/test/constraint/equal_radius/normal_v22.slvs @@ -0,0 +1,349 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.normal.v=00050020 +Entity.distance.v=00050040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=130 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/equal_radius/test.cpp b/test/constraint/equal_radius/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/constraint/equal_radius/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/constraint/horizontal/line.png b/test/constraint/horizontal/line.png new file mode 100644 index 0000000000000000000000000000000000000000..cd911e5a9c6f4423bb5b3d0b669208c6b0afba60 GIT binary patch literal 4274 zcmeHLdr(t%8vO_mfrv7qfD#^c*63C!s375Cu~ix^NCgCg0gYlo79s2)5Dbrmvb(aP zjO!LCvd~JQH3br(yh*rfOBBJl8UjO1fTB_eFM*H}2-yULY1WlGJF|c6jDKW)Gs!pi ze)oLmJLlYF9`+>|8*MTI0ONxP_WJ{%h?b22V1i=<+}1OLy?F=<3{2IxKKVZ?V%tPV9PeaC&knH0 z>gVV91$>3DmMbf#q)17sYaz!j$B($O?(|v6W5uy}Y_|2GPUBhwo!@FMt9Y;-#b7vx zx@bRyw^H$iD?moxYoE5|W`)=ty1bSO*j!}i&?>-vn!X4NJ;_^GlQGin9Hr!@K$p8jKBPP3TqureWxw6!oBdgRsTubLxu#&van(Bg}C6Qey ztU1zALNkTNxiV(%=plF#@ltVD)Oxi%AM30M&;{&0J&f(s2sxVm*n|l0ILNkFW|$Qm zSP)5*DSs8lzBkFxj;@;*ek;zDh}0{S;+y1BQ`*dI(Pm6xHiBw-A)@xAB&XazeCF1q z`Vu{e6Q=OKe_(FTt~6PoQj~K-CCS%VQ}zkfNVhbodc?hlPq5xsmAi-E7g1EjDWfPO zf}8ugA6gQ1Lsv6!ovi9^;fY(ap=?S|dv232>v&A6nMsGh(vz#qf=_;qFx*bXp)eLa zH;F(bFv@op+iMyN?LlLQOY}>F#+&ra_U7H#&>Ou8(tp@+xMLU=dFb(gW!F*48kkP;Pu3H zWJO20mM`zErts_yrneji5WcW=DZ912lLeu&9k?8OS-m)U7nb)nlOj``$rXkInC@EK$JcK_%W6B%Miao_ z+@X7a?FBUWo^3;z`MWFMlbxlMuZ78tE^KD$rLuRw(jUv8lDo%#St@PmVgx#ep0=0q zZLV&s1hPl}s-TW@R{~|KD_gz?GB!>xsDpJkKP%DmnLpBA#M%-gFb|dAk2#b-U2#+1Pw*~ORYjFdKb#3kNVC%EZLLQ|v9euL=m;-dWJ2|G`ppWt|?z~fs7c41%jdRkA@)W$*#P&bD)qowC7V}-RL zb(hPsrxTSH??19SY4Z=&cl;SYsf5%VWd((|T~X5F18z}HdxoO-ijB&%t>fgAv4WZG zuT0LIQVJ<*1i9VtV3kqadz`lmSk7 zp8aitxZ%>YIL$=TSe{E&ecc()trUtlS-TH4%DZ}I-IAx8_PqOIPWNH)i(|?oW8Qr) zR#T}lR;<6M%&}{Dw!Gw8gT`|{e4qymalEJw2e>r&bERkp>zoX8qRMyrL2-<~Z%8UP&dE?MG>TFVfw5jfbE` Q{a*we^zz+b@gX_=UqeK}y#N3J literal 0 HcmV?d00001 diff --git a/test/constraint/horizontal/line.slvs b/test/constraint/horizontal/line.slvs new file mode 100644 index 0000000..cebb650 --- /dev/null +++ b/test/constraint/horizontal/line.slvs @@ -0,0 +1,288 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/horizontal/line_v20.slvs b/test/constraint/horizontal/line_v20.slvs new file mode 100644 index 0000000..8909c0f --- /dev/null +++ b/test/constraint/horizontal/line_v20.slvs @@ -0,0 +1,286 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/horizontal/line_v22.slvs b/test/constraint/horizontal/line_v22.slvs new file mode 100644 index 0000000..8b2dd43 --- /dev/null +++ b/test/constraint/horizontal/line_v22.slvs @@ -0,0 +1,288 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/horizontal/pt_pt.png b/test/constraint/horizontal/pt_pt.png new file mode 100644 index 0000000000000000000000000000000000000000..7614ea815f7303cc0a08f2839bf9362fc9618dae GIT binary patch literal 4268 zcmeI0dr*^C8pclo;T|rfA|eYag?3l216xv+ixr_XL0T0G1VStV<(7b46cR$9s8uK| zYgev?>7pQ)KmS zJM8II{4+{himj<|b5sXZeZybd7KGxpkkmn|#pvrS>_6I4hqr+HZvA3T zB>B+Kccc_t=RQNr3!$H$+0qbKtp-qk)m)e7(O?p1(_SGQW?byodIs000J~VrN+!bb zh30;ufp$%*^gn8B!0(s5m1CJdoDvPF7R7j2_R8Z?5N`t4-Z5E2@w|99%P{IG9J9)2 zWnUqJ3n4g)8f6q*3c-8l1zR~-w(Heu+3Jzwogxiuyea#0@d(F`xJ}FEx^5<70f}iI z50bB`rEIybadwQcA`$bh$^9r}MbbEgW7fuvEmQXWU|ixdpQ`I7Et?Nd=ZA)`o{2d? z#3Pd&R7={(v_Zz2PD5k{MM~(HOidFlO+>;z^l4v|GAj(mvnXHCvvwN<$c<*PYP8D# zZv70QSh29FIH(gBgpWoHS<=Bx3=I(bf&Lo})Y@LbF4dx3Y!>9G@Zm?l<$g-aC=48L zYBdH`mNabtrHaotkfI?*OPT-6I@V7jLO>{<7IVJ_2J(()J&g|3gd(y^el&})VXM!7 z@iL1S#c?dx!m)PkaEl)AirgY4@=oko#(9SUWcvWT>fe*q$iFOETc6*-3=p zu^3;PfbsX_9jM4u#%FIkFI@R~D0xBeJZg!vsowIlu1)5Tssae}@@?{}7&f5we_0Ge z+VzS+PW>*#j$Z3w1mds6s2Kq$Q#rKByZCtxIxskzB3$mXrL_WJ74c)+y*Sp>JD{K; z#t{`@dV9U29@;OfnGGh5=qpv6@)aOWrH2`;0t{!7^>K!Q07{Y&4bEGG@wem+JG=(f zMw1o*@IraX@qzJBFvP}BUa(!vG|DwJ-xg5M$$96(bqFOpqN(UHka6-N1M#c}$&uN$`eD!g&bn%9gk`P|Ogr zUNghp3B>Elzx}BI0ZhLi&h)cv0#wwG!&{jm%yAgl3Flv~V)m{iF51OL& zp^lxmHq76bu?%zf``k33u`-J5G@E=>6K(m^ zG_8~CF@lx0GJAVCDT2f&>9GRWP*S@cP+piGxAoS+Q&9HLur=&Ryy(Txki06Lw!YEoZ#;dFDy)Xko+(})>%=C?>J zi@A1ThUgu)=n)eCBuxdfEVH1G=3Hv_?61VB4|shg8V!3wG0WQ{E69)GI&04juCA!q ze~}RSsffq_xP$b~!sF!?OC%_?C*a$s_ge6A1L+ZV`E6(y@r{ET?w=+;*hL=jV)k`N z?QW8oqSpi!OKrU$@zUB0`>LYEW>ReES#dej%d$KJq&ea0>_i=}n{laUr+JIye-S$; z;&o2kIhjL8bCz@f>hdTyTbwnyJFMO1YGRk9Mo6*b)e~2XhkS#bBPf)5#kH3(LcdJO zYZ`BhNNv>xwofmQsYoR!+N_svIO_j?i=SgZK4$aI)?@HB`CAC!eejS+!@Kybe*;=Q Bs4D;f literal 0 HcmV?d00001 diff --git a/test/constraint/horizontal/pt_pt.slvs b/test/constraint/horizontal/pt_pt.slvs new file mode 100644 index 0000000..ac91222 --- /dev/null +++ b/test/constraint/horizontal/pt_pt.slvs @@ -0,0 +1,287 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/horizontal/pt_pt_v20.slvs b/test/constraint/horizontal/pt_pt_v20.slvs new file mode 100644 index 0000000..6a38678 --- /dev/null +++ b/test/constraint/horizontal/pt_pt_v20.slvs @@ -0,0 +1,287 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/horizontal/pt_pt_v22.slvs b/test/constraint/horizontal/pt_pt_v22.slvs new file mode 100644 index 0000000..cadcdba --- /dev/null +++ b/test/constraint/horizontal/pt_pt_v22.slvs @@ -0,0 +1,287 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/horizontal/test.cpp b/test/constraint/horizontal/test.cpp new file mode 100644 index 0000000..c08b5e1 --- /dev/null +++ b/test/constraint/horizontal/test.cpp @@ -0,0 +1,33 @@ +#include "harness.h" + +TEST_CASE(line_roundtrip) { + CHECK_LOAD("line.slvs"); + CHECK_RENDER("line.png"); + CHECK_SAVE("line.slvs"); +} + +TEST_CASE(line_migrate_from_v20) { + CHECK_LOAD("line_v20.slvs"); + CHECK_SAVE("line.slvs"); +} + +TEST_CASE(line_migrate_from_v22) { + CHECK_LOAD("line_v22.slvs"); + CHECK_SAVE("line.slvs"); +} + +TEST_CASE(pt_pt_roundtrip) { + CHECK_LOAD("pt_pt.slvs"); + CHECK_RENDER("pt_pt.png"); + CHECK_SAVE("pt_pt.slvs"); +} + +TEST_CASE(pt_pt_migrate_from_v20) { + CHECK_LOAD("pt_pt_v20.slvs"); + CHECK_SAVE("pt_pt.slvs"); +} + +TEST_CASE(pt_pt_migrate_from_v22) { + CHECK_LOAD("pt_pt_v22.slvs"); + CHECK_SAVE("pt_pt.slvs"); +} diff --git a/test/constraint/length_difference/normal.png b/test/constraint/length_difference/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..eec393188526e5735a171e5812ce60e1106f9dc7 GIT binary patch literal 4462 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

|{q0O&uARKPU{t zqiKRpq5n}*kYqyxv7n1;R%4sp;5?YqA6md79Mk7r5tanLjZ#!274((Vf_LB#IEh>Qq(TVdXd=eyZ-@ISi zZbPd^k~c!5evZiHUVn4?q41SJ>>!JtDr`E?c#Gqc8=pxPhDnWWcg^=h&R6ARNwIP+ zOLa#9g0=aYnbgH?wi-nw6 zkSMxwm_wIGHk_eiIg!h-G2qBddWKu5ueRW3$x}Q&vVN!lms7K$rnJBTlfIv*4vcqo zE-Uu7?@hWVN_<)4ue$v@dbzNpLFp`t`K@quuf$(9Ku}RT*99IizmBrLyYpx@jCXh> mnRkCqHJ{Mzzvmr?0=YG>rCrUr)(Lty1GYGBcewivF7_V?&t7)` literal 0 HcmV?d00001 diff --git a/test/constraint/length_ratio/normal.slvs b/test/constraint/length_ratio/normal.slvs new file mode 100644 index 0000000..0286fc1 --- /dev/null +++ b/test/constraint/length_ratio/normal.slvs @@ -0,0 +1,342 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=15.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=51 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=0.50000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=-10.00000000000000000000 +Constraint.disp.offset.y=5.00000000000000000000 +AddConstraint + diff --git a/test/constraint/length_ratio/normal_v20.slvs b/test/constraint/length_ratio/normal_v20.slvs new file mode 100644 index 0000000..a73f06b --- /dev/null +++ b/test/constraint/length_ratio/normal_v20.slvs @@ -0,0 +1,340 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=15.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=51 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=0.50000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=-10.00000000000000000000 +Constraint.disp.offset.y=5.00000000000000000000 +AddConstraint + diff --git a/test/constraint/length_ratio/normal_v22.slvs b/test/constraint/length_ratio/normal_v22.slvs new file mode 100644 index 0000000..08957de --- /dev/null +++ b/test/constraint/length_ratio/normal_v22.slvs @@ -0,0 +1,342 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=15.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=51 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=0.50000000000000000000 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=-10.00000000000000000000 +Constraint.disp.offset.y=5.00000000000000000000 +AddConstraint + diff --git a/test/constraint/length_ratio/reference.png b/test/constraint/length_ratio/reference.png new file mode 100644 index 0000000000000000000000000000000000000000..5a5bfc9a074c0908f4d9d6581f78e97d724d5d5a GIT binary patch literal 4616 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 ftn_=l(o->*!kW$ literal 0 HcmV?d00001 diff --git a/test/constraint/parallel/free_in_3d.slvs b/test/constraint/parallel/free_in_3d.slvs new file mode 100644 index 0000000..1a054ed --- /dev/null +++ b/test/constraint/parallel/free_in_3d.slvs @@ -0,0 +1,295 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=40000001 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=121 +Constraint.group.v=00000002 +Constraint.valP.v=40000001 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00030020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/parallel/free_in_3d_v20.slvs b/test/constraint/parallel/free_in_3d_v20.slvs new file mode 100644 index 0000000..421eb11 --- /dev/null +++ b/test/constraint/parallel/free_in_3d_v20.slvs @@ -0,0 +1,290 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=121 +Constraint.group.v=00000002 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00030020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/parallel/free_in_3d_v22.slvs b/test/constraint/parallel/free_in_3d_v22.slvs new file mode 100644 index 0000000..1628203 --- /dev/null +++ b/test/constraint/parallel/free_in_3d_v22.slvs @@ -0,0 +1,290 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=121 +Constraint.group.v=00000002 +Constraint.entityA.v=00040000 +Constraint.entityB.v=00030020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/parallel/normal.png b/test/constraint/parallel/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..599c82bd97bf2d94813ad1045166c7d474efa1b8 GIT binary patch literal 4686 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 KtvPucA^tyPa1uHI literal 0 HcmV?d00001 diff --git a/test/constraint/perpendicular/normal.slvs b/test/constraint/perpendicular/normal.slvs new file mode 100644 index 0000000..a0d5930 --- /dev/null +++ b/test/constraint/perpendicular/normal.slvs @@ -0,0 +1,339 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=122 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/perpendicular/normal_v20.slvs b/test/constraint/perpendicular/normal_v20.slvs new file mode 100644 index 0000000..905594d --- /dev/null +++ b/test/constraint/perpendicular/normal_v20.slvs @@ -0,0 +1,337 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=122 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/perpendicular/normal_v22.slvs b/test/constraint/perpendicular/normal_v22.slvs new file mode 100644 index 0000000..342a049 --- /dev/null +++ b/test/constraint/perpendicular/normal_v22.slvs @@ -0,0 +1,339 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=122 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.entityB.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/perpendicular/test.cpp b/test/constraint/perpendicular/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/constraint/perpendicular/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/constraint/points_coincident/free_in_3d.png b/test/constraint/points_coincident/free_in_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..df5a660e0e81c60c9c9b3da6edf17cc0009e979b GIT binary patch literal 4241 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 NJFM-k3bsHoe*hyMKyv^9 literal 0 HcmV?d00001 diff --git a/test/constraint/proj_pt_distance/normal.slvs b/test/constraint/proj_pt_distance/normal.slvs new file mode 100644 index 0000000..1658c3e --- /dev/null +++ b/test/constraint/proj_pt_distance/normal.slvs @@ -0,0 +1,290 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=34 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=5.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.entityA.v=00020020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.y=-4.00000000000000000000 +AddConstraint + diff --git a/test/constraint/proj_pt_distance/normal_v20.slvs b/test/constraint/proj_pt_distance/normal_v20.slvs new file mode 100644 index 0000000..7dd3084 --- /dev/null +++ b/test/constraint/proj_pt_distance/normal_v20.slvs @@ -0,0 +1,293 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=34 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=5.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.entityA.v=00020020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=0.00000000000000000000 +Constraint.disp.offset.y=-4.00000000000000000000 +AddConstraint + diff --git a/test/constraint/proj_pt_distance/normal_v22.slvs b/test/constraint/proj_pt_distance/normal_v22.slvs new file mode 100644 index 0000000..01ba48e --- /dev/null +++ b/test/constraint/proj_pt_distance/normal_v22.slvs @@ -0,0 +1,290 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=34 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=5.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.entityA.v=00020020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.y=-4.00000000000000000000 +AddConstraint + diff --git a/test/constraint/proj_pt_distance/reference.png b/test/constraint/proj_pt_distance/reference.png new file mode 100644 index 0000000000000000000000000000000000000000..571927b419b114e7171ffb4e9fee859ca0e13dac GIT binary patch literal 4566 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 DxI9+) literal 0 HcmV?d00001 diff --git a/test/constraint/proj_pt_distance/reference.slvs b/test/constraint/proj_pt_distance/reference.slvs new file mode 100644 index 0000000..d1203b9 --- /dev/null +++ b/test/constraint/proj_pt_distance/reference.slvs @@ -0,0 +1,290 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=34 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=5.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.entityA.v=00020020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.y=-4.00000000000000000000 +AddConstraint + diff --git a/test/constraint/proj_pt_distance/reference_v20.slvs b/test/constraint/proj_pt_distance/reference_v20.slvs new file mode 100644 index 0000000..d5f3860 --- /dev/null +++ b/test/constraint/proj_pt_distance/reference_v20.slvs @@ -0,0 +1,292 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=34 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=5.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.entityA.v=00020020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.y=-4.00000000000000000000 +AddConstraint + diff --git a/test/constraint/proj_pt_distance/reference_v22.slvs b/test/constraint/proj_pt_distance/reference_v22.slvs new file mode 100644 index 0000000..6461182 --- /dev/null +++ b/test/constraint/proj_pt_distance/reference_v22.slvs @@ -0,0 +1,290 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=34 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=5.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.entityA.v=00020020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.y=-4.00000000000000000000 +AddConstraint + diff --git a/test/constraint/proj_pt_distance/test.cpp b/test/constraint/proj_pt_distance/test.cpp new file mode 100644 index 0000000..7e2a06e --- /dev/null +++ b/test/constraint/proj_pt_distance/test.cpp @@ -0,0 +1,33 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(reference_roundtrip) { + CHECK_LOAD("reference.slvs"); + CHECK_RENDER("reference.png"); + CHECK_SAVE("reference.slvs"); +} + +TEST_CASE(reference_migrate_from_v20) { + CHECK_LOAD("reference_v20.slvs"); + CHECK_SAVE("reference.slvs"); +} + +TEST_CASE(reference_migrate_from_v22) { + CHECK_LOAD("reference_v22.slvs"); + CHECK_SAVE("reference.slvs"); +} diff --git a/test/constraint/pt_face_distance/normal.png b/test/constraint/pt_face_distance/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..5fe42722bbb52363aa3c4bcff7a982c1ca233260 GIT binary patch literal 4314 zcmeI0dr(t%7RP^yNexDnH)s*CBNQY+6d$;Vs61TCLn{hdfpAfg-H8DN5rF~;Dhz@$ z-BpN{V#Nmvgv3X9L?S^{tOgO0%OeobEXL)jrUW%4>;<;zTurSz)1CdNGsz@#^GohI z=lA`6&*vn2OTZ@GISc0i0Cau+?70;HEM@9R_zbyna}TQ<0KKz5o*T9uEFA9H5NGbS zF!QNMCp<&<_?lTau@5>$i!Od+UF>qp^I*f@?K`5ZM48SE=66Syct67^b_zZ!zRi>^A(b{=~ocs)vjAbz|4e1 z34^|mYc>MVdK+zV}a(DfuG#JDhd{BYs+pi}Eq6!pb zN-2t(1^n*gwA7BVubehEhtXp6eh^4j+nulgve6p3rtIY7k(a3$p$|F(WS3aMEfRlJ zbVxChigW?l&QROd-Mv!LW)Hjr_xI?bN;L*|1qld%af*dX>iu08y5wI-#)B_E5D7%} zN?DCZ)iQzEpR@`H&--apj#(o=hy%?0TM|4_H-tlIA!AMbExwUAt!DG!RIGom7pt`7*)`DZ(x433>B?H88j4GR_i65DrV6 zryKVT2XwruK`6!sLj!{394MM2y!|q*%F1Mc;ob{9##Cn^n$n2 zJonN95PL$Hv0<&T$B&#fvUqTi6>=EqLJ5YX`3vwxPse-HIaI9hYmG;WY!V^cGwV`I zQCFvR+Ia{+_l^Y;&@YP}HbS;_Dn-bc0XRNo0?&CQ>k?zMN6_QK^I@1b^plXaH*U5! zgm$0V$FLq(W)sbrh#8PM#jBra+Tx!1Cgc<0=_+2b4%pP@&t1FQ2(V5}N3ZFjo&TIS z_s$`2roI+$MQ)b)%x6SMKwOg5dUYFzVUM^NtCcVD%zwm>_Nr53iHs!n;G_r9@g9cKYeqVEd7rrz4d^jd_+KH1^oKN3J^l>}o3}UfAu&w(VP{ z3q-!5U!NZdI}D;?b7I3YtG!<2TOGlfka0|&lLyYkC@x3uzhD?SUzv8o6y5I1h~>p8 z#Q-=pX=~|7t6|fA{2RLG&9w7A7X3!eHbI(-f6)3m-1VOvBssxYj@ zSxnAVE9hee)g|Lp0j|SA)BBQXP5%18&XtY4;46Nl|e8FFgLXI*bDa8&jd=Rl~0a`LA!QuV-R>746UI2)0Yu)&da4=;4 zV5?S~2!lF(F~0(bA5qe8f}s{3pFyK@cKm1piU1Kapw0o%R*f;D0AEBygkg`W+aR0A zPSyv&d6_nH`q*4x)`&Qn7P>ntIHTs_?Hz zD?Zm4aacG+ZzCdb8Ei^9nPTT@0$yfR#2_c~<%g3EtDT=;7=%&`Eatf#kdH)(eS?v$ z6#bvI@vL|xtx~Yx7zl4`C2Z~H2S409+-1R@59In3SL^G2m|yhDa~7nk=>}dVAa@fq z{3K$xjsq~u5_T}U*DFN?WKK1rN*#N#ax5VJQr7Bl{@38zO7AP7VZZVHpWCL=U z&zF?Vq66<*nmgDHKP-GD1E3vxEc~unRc#OixG`9o%#RKFh~-n(2_HYT10!VN;CWN8 z9XR+Q3weE$ERUWHt5M$33kGDz=K8Kv4KaDMx}~n`!6Ed}qUplZzO68(WX05{4LNjf z7Hn5O*;V*%A7D5S+j9eW74x%NJlqu>GD&J$%W5XBsD6?ow7NUDykzZuOq%UgzY4%G zT^wv-<|Bt7kBnfchmGKG{GAZSW-zt_DG2dEvDr`xZt zRgLDQHrbs?e7Ht3R0KjIY!lGQ6a(So>3l8kG zWR6jmWZNL)s&u6OPq~~D3)?v$c7yaYco!ncEqg`6423byTPnZ5YAtR%5+P8e9NxaV<=o0<5kN;jnzQW!AhpY3>E$FgG Q{<8o+UICsJ8=;JU17L2V3jhEB literal 0 HcmV?d00001 diff --git a/test/constraint/pt_face_distance/normal.slvs b/test/constraint/pt_face_distance/normal.slvs new file mode 100644 index 0000000..f2fd334 --- /dev/null +++ b/test/constraint/pt_face_distance/normal.slvs @@ -0,0 +1,713 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=00020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000003 +Group.type=5100 +Group.order=2 +Group.name=extrude +Group.activeWorkplane.v=00010000 +Group.opA.v=00000002 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.predef.entityB.v=00020000 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00040000 1002 + 2 00040001 1002 + 3 00040020 1002 + 4 00040040 1002 + 5 00040000 1001 + 6 00040001 1001 + 7 00040020 1001 + 8 00040040 1001 + 9 00040001 1003 + 10 80020000 1002 + 11 80020000 1001 + 12 80020001 1002 + 13 80020002 1002 + 14 80020001 1001 + 15 80020002 1001 + 16 80020002 1003 + 17 00000000 1001 + 18 00000000 1002 +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030000 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030001 +AddParam + +Param.h.v.=80030002 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=00020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=00010000 +Request.group.v=00000003 +Request.construction=1 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=00020000 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=1 +Entity.workplane.v=00010000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030001 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.normal.v=80030003 +Entity.distance.v=80030004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030004 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.normal.v=80030007 +Entity.distance.v=80030008 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030007 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.point[1].v=80030002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=2010 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2010 +Entity.construction=1 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=1 +Entity.point[0].v=8003000f +Entity.point[1].v=8003000d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actPoint.x=10.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=00020000 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=33 +Constraint.group.v=00000003 +Constraint.workplane.v=00010000 +Constraint.valA=-10.00000000000000000000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=80030012 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Surface 00000001 00646464 80030012 1 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000a 0 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 +TrimBy 00000001 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000004 0 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +TrimBy 00000007 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 +AddSurface +Surface 00000002 00646464 80030011 1 1 +SCtrl 0 0 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000002 1 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000b 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 +TrimBy 00000008 1 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +AddSurface +Surface 00000003 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000002 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000001 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000c 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000003 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddSurface +Surface 00000004 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +TrimBy 00000005 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000004 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000003 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000006 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddSurface +Surface 00000005 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000008 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000007 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000006 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000009 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddSurface +Surface 00000006 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +TrimBy 0000000b 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +TrimBy 0000000a 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000009 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 0000000c 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddSurface +Curve 00000001 1 2 00000001 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 0.00000000000000000000 4.91652684208287205081 0.90981526206072360630 +CurvePt 0 0.00000000000000000000 4.64894150531215188948 1.84047354780936389673 +CurvePt 0 0.00000000000000000000 4.18497755609325672310 2.73604876692571252761 +CurvePt 0 0.00000000000000000000 3.53553390593273775266 3.53553390593273775266 +CurvePt 0 0.00000000000000000000 2.73604876692571252761 4.18497755609325672310 +CurvePt 0 0.00000000000000000000 1.84047354780936411878 4.64894150531215188948 +CurvePt 0 0.00000000000000000000 0.90981526206072382834 4.91652684208287205081 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000002 1 2 00000002 00000003 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 10.00000000000000000000 4.91652684208287205081 0.90981526206072360630 +CurvePt 0 10.00000000000000000000 4.64894150531215188948 1.84047354780936389673 +CurvePt 0 10.00000000000000000000 4.18497755609325672310 2.73604876692571252761 +CurvePt 0 10.00000000000000000000 3.53553390593273775266 3.53553390593273775266 +CurvePt 0 10.00000000000000000000 2.73604876692571252761 4.18497755609325672310 +CurvePt 0 10.00000000000000000000 1.84047354780936411878 4.64894150531215188948 +CurvePt 0 10.00000000000000000000 0.90981526206072382834 4.91652684208287205081 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000003 1 1 00000003 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000004 1 2 00000001 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 0.00000000000000000000 -0.90981526206072327323 4.91652684208287205081 +CurvePt 0 0.00000000000000000000 -1.84047354780936323060 4.64894150531215188948 +CurvePt 0 0.00000000000000000000 -2.73604876692571208352 4.18497755609325672310 +CurvePt 0 0.00000000000000000000 -3.53553390593273775266 3.53553390593273775266 +CurvePt 0 0.00000000000000000000 -4.18497755609325672310 2.73604876692571297170 +CurvePt 0 0.00000000000000000000 -4.64894150531215188948 1.84047354780936434082 +CurvePt 0 0.00000000000000000000 -4.91652684208287205081 0.90981526206072405039 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000005 1 2 00000002 00000004 +CCtrl 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 10.00000000000000000000 -0.90981526206072327323 4.91652684208287205081 +CurvePt 0 10.00000000000000000000 -1.84047354780936323060 4.64894150531215188948 +CurvePt 0 10.00000000000000000000 -2.73604876692571208352 4.18497755609325672310 +CurvePt 0 10.00000000000000000000 -3.53553390593273775266 3.53553390593273775266 +CurvePt 0 10.00000000000000000000 -4.18497755609325672310 2.73604876692571297170 +CurvePt 0 10.00000000000000000000 -4.64894150531215188948 1.84047354780936434082 +CurvePt 0 10.00000000000000000000 -4.91652684208287205081 0.90981526206072405039 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000006 1 1 00000004 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000007 1 2 00000001 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 0.00000000000000000000 -4.91652684208287205081 -0.90981526206072305119 +CurvePt 0 0.00000000000000000000 -4.64894150531215188948 -1.84047354780936300855 +CurvePt 0 0.00000000000000000000 -4.18497755609325672310 -2.73604876692571208352 +CurvePt 0 0.00000000000000000000 -3.53553390593273864084 -3.53553390593273730858 +CurvePt 0 0.00000000000000000000 -2.73604876692571297170 -4.18497755609325672310 +CurvePt 0 0.00000000000000000000 -1.84047354780936478491 -4.64894150531215188948 +CurvePt 0 0.00000000000000000000 -0.90981526206072438345 -4.91652684208287205081 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000008 1 2 00000002 00000005 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 10.00000000000000000000 -4.91652684208287205081 -0.90981526206072305119 +CurvePt 0 10.00000000000000000000 -4.64894150531215188948 -1.84047354780936300855 +CurvePt 0 10.00000000000000000000 -4.18497755609325672310 -2.73604876692571208352 +CurvePt 0 10.00000000000000000000 -3.53553390593273864084 -3.53553390593273730858 +CurvePt 0 10.00000000000000000000 -2.73604876692571297170 -4.18497755609325672310 +CurvePt 0 10.00000000000000000000 -1.84047354780936478491 -4.64894150531215188948 +CurvePt 0 10.00000000000000000000 -0.90981526206072438345 -4.91652684208287205081 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000009 1 1 00000005 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 0000000a 1 2 00000001 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 0.00000000000000000000 0.90981526206072282914 -4.91652684208287205081 +CurvePt 0 0.00000000000000000000 1.84047354780936300855 -4.64894150531215188948 +CurvePt 0 0.00000000000000000000 2.73604876692571163943 -4.18497755609325672310 +CurvePt 0 0.00000000000000000000 3.53553390593273730858 -3.53553390593273864084 +CurvePt 0 0.00000000000000000000 4.18497755609325583492 -2.73604876692571297170 +CurvePt 0 0.00000000000000000000 4.64894150531215188948 -1.84047354780936500696 +CurvePt 0 0.00000000000000000000 4.91652684208287205081 -0.90981526206072471652 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000b 1 2 00000002 00000006 +CCtrl 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 10.00000000000000000000 0.90981526206072282914 -4.91652684208287205081 +CurvePt 0 10.00000000000000000000 1.84047354780936300855 -4.64894150531215188948 +CurvePt 0 10.00000000000000000000 2.73604876692571163943 -4.18497755609325672310 +CurvePt 0 10.00000000000000000000 3.53553390593273730858 -3.53553390593273864084 +CurvePt 0 10.00000000000000000000 4.18497755609325583492 -2.73604876692571297170 +CurvePt 0 10.00000000000000000000 4.64894150531215188948 -1.84047354780936500696 +CurvePt 0 10.00000000000000000000 4.91652684208287205081 -0.90981526206072471652 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000c 1 1 00000006 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve diff --git a/test/constraint/pt_face_distance/normal_v20.slvs b/test/constraint/pt_face_distance/normal_v20.slvs new file mode 100644 index 0000000..c8a4c23 --- /dev/null +++ b/test/constraint/pt_face_distance/normal_v20.slvs @@ -0,0 +1,712 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=00020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000003 +Group.type=5100 +Group.order=2 +Group.name=extrude +Group.activeWorkplane.v=00010000 +Group.opA.v=00000002 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.predef.entityB.v=00020000 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00040000 1002 + 2 00040001 1002 + 3 00040020 1002 + 4 00040040 1002 + 5 00040000 1001 + 6 00040001 1001 + 7 00040020 1001 + 8 00040040 1001 + 9 00040001 1003 + 10 80020000 1002 + 11 80020000 1001 + 12 80020001 1002 + 13 80020002 1002 + 14 80020001 1001 + 15 80020002 1001 + 16 80020002 1003 + 17 00000000 1001 + 18 00000000 1002 +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030000 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030001 +AddParam + +Param.h.v.=80030002 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=00020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=00010000 +Request.group.v=00000003 +Request.construction=1 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=00020000 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00010000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030001 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.normal.v=80030003 +Entity.distance.v=80030004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030004 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.normal.v=80030007 +Entity.distance.v=80030008 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030007 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.point[1].v=80030002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.point[1].v=8003000d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actPoint.x=10.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=00020000 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=33 +Constraint.group.v=00000003 +Constraint.workplane.v=00010000 +Constraint.valA=-10.00000000000000000000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=80030012 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Surface 00000001 00646464 80030012 1 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000a 0 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 +TrimBy 00000001 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000004 0 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +TrimBy 00000007 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 +AddSurface +Surface 00000002 00646464 80030011 1 1 +SCtrl 0 0 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000002 1 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000b 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 +TrimBy 00000008 1 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +AddSurface +Surface 00000003 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 2 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000002 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000003 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000001 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000c 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000004 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -4.99999999999999910000 5.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 1 1 10.00000000000000000000 -4.99999999999999910000 5.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 2 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +TrimBy 00000005 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000006 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000004 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000003 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddSurface +Surface 00000005 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -5.00000000000000090000 -4.99999999999999910000 Weight 0.70710678118654757000 +SCtrl 1 1 10.00000000000000000000 -5.00000000000000090000 -4.99999999999999910000 Weight 0.70710678118654757000 +SCtrl 2 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000008 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000009 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000007 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000006 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddSurface +Surface 00000006 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 4.99999999999999910000 -5.00000000000000090000 Weight 0.70710678118654757000 +SCtrl 1 1 10.00000000000000000000 4.99999999999999910000 -5.00000000000000090000 Weight 0.70710678118654757000 +SCtrl 2 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +TrimBy 0000000b 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +TrimBy 0000000c 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +TrimBy 0000000a 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000009 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddSurface +Curve 00000001 1 2 00000001 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 0.00000000000000000000 4.91652684208287210000 0.90981526206072361000 +CurvePt 0 0.00000000000000000000 4.64894150531215190000 1.84047354780936390000 +CurvePt 0 0.00000000000000000000 4.18497755609325670000 2.73604876692571250000 +CurvePt 0 0.00000000000000000000 3.53553390593273780000 3.53553390593273780000 +CurvePt 0 0.00000000000000000000 2.73604876692571250000 4.18497755609325670000 +CurvePt 0 0.00000000000000000000 1.84047354780936410000 4.64894150531215190000 +CurvePt 0 0.00000000000000000000 0.90981526206072383000 4.91652684208287210000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000002 1 2 00000002 00000003 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 10.00000000000000000000 4.91652684208287210000 0.90981526206072361000 +CurvePt 0 10.00000000000000000000 4.64894150531215190000 1.84047354780936390000 +CurvePt 0 10.00000000000000000000 4.18497755609325670000 2.73604876692571250000 +CurvePt 0 10.00000000000000000000 3.53553390593273780000 3.53553390593273780000 +CurvePt 0 10.00000000000000000000 2.73604876692571250000 4.18497755609325670000 +CurvePt 0 10.00000000000000000000 1.84047354780936410000 4.64894150531215190000 +CurvePt 0 10.00000000000000000000 0.90981526206072383000 4.91652684208287210000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000003 1 1 00000003 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000004 1 2 00000001 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -4.99999999999999910000 5.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 0.00000000000000000000 -0.90981526206072327000 4.91652684208287210000 +CurvePt 0 0.00000000000000000000 -1.84047354780936320000 4.64894150531215190000 +CurvePt 0 0.00000000000000000000 -2.73604876692571210000 4.18497755609325670000 +CurvePt 0 0.00000000000000000000 -3.53553390593273780000 3.53553390593273780000 +CurvePt 0 0.00000000000000000000 -4.18497755609325670000 2.73604876692571300000 +CurvePt 0 0.00000000000000000000 -4.64894150531215190000 1.84047354780936430000 +CurvePt 0 0.00000000000000000000 -4.91652684208287210000 0.90981526206072405000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000005 1 2 00000002 00000004 +CCtrl 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -4.99999999999999910000 5.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 10.00000000000000000000 -0.90981526206072327000 4.91652684208287210000 +CurvePt 0 10.00000000000000000000 -1.84047354780936320000 4.64894150531215190000 +CurvePt 0 10.00000000000000000000 -2.73604876692571210000 4.18497755609325670000 +CurvePt 0 10.00000000000000000000 -3.53553390593273780000 3.53553390593273780000 +CurvePt 0 10.00000000000000000000 -4.18497755609325670000 2.73604876692571300000 +CurvePt 0 10.00000000000000000000 -4.64894150531215190000 1.84047354780936430000 +CurvePt 0 10.00000000000000000000 -4.91652684208287210000 0.90981526206072405000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000006 1 1 00000004 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000007 1 2 00000001 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -5.00000000000000090000 -4.99999999999999910000 Weight 0.70710678118654757000 +CCtrl 2 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 0.00000000000000000000 -4.91652684208287210000 -0.90981526206072305000 +CurvePt 0 0.00000000000000000000 -4.64894150531215190000 -1.84047354780936300000 +CurvePt 0 0.00000000000000000000 -4.18497755609325670000 -2.73604876692571210000 +CurvePt 0 0.00000000000000000000 -3.53553390593273860000 -3.53553390593273730000 +CurvePt 0 0.00000000000000000000 -2.73604876692571300000 -4.18497755609325670000 +CurvePt 0 0.00000000000000000000 -1.84047354780936480000 -4.64894150531215190000 +CurvePt 0 0.00000000000000000000 -0.90981526206072438000 -4.91652684208287210000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000008 1 2 00000002 00000005 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000090000 -4.99999999999999910000 Weight 0.70710678118654757000 +CCtrl 2 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 10.00000000000000000000 -4.91652684208287210000 -0.90981526206072305000 +CurvePt 0 10.00000000000000000000 -4.64894150531215190000 -1.84047354780936300000 +CurvePt 0 10.00000000000000000000 -4.18497755609325670000 -2.73604876692571210000 +CurvePt 0 10.00000000000000000000 -3.53553390593273860000 -3.53553390593273730000 +CurvePt 0 10.00000000000000000000 -2.73604876692571300000 -4.18497755609325670000 +CurvePt 0 10.00000000000000000000 -1.84047354780936480000 -4.64894150531215190000 +CurvePt 0 10.00000000000000000000 -0.90981526206072438000 -4.91652684208287210000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000009 1 1 00000005 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 0000000a 1 2 00000001 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 4.99999999999999910000 -5.00000000000000090000 Weight 0.70710678118654757000 +CCtrl 2 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 0.00000000000000000000 0.90981526206072283000 -4.91652684208287210000 +CurvePt 0 0.00000000000000000000 1.84047354780936300000 -4.64894150531215190000 +CurvePt 0 0.00000000000000000000 2.73604876692571160000 -4.18497755609325670000 +CurvePt 0 0.00000000000000000000 3.53553390593273730000 -3.53553390593273860000 +CurvePt 0 0.00000000000000000000 4.18497755609325580000 -2.73604876692571300000 +CurvePt 0 0.00000000000000000000 4.64894150531215190000 -1.84047354780936500000 +CurvePt 0 0.00000000000000000000 4.91652684208287210000 -0.90981526206072472000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000b 1 2 00000002 00000006 +CCtrl 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 4.99999999999999910000 -5.00000000000000090000 Weight 0.70710678118654757000 +CCtrl 2 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 10.00000000000000000000 0.90981526206072283000 -4.91652684208287210000 +CurvePt 0 10.00000000000000000000 1.84047354780936300000 -4.64894150531215190000 +CurvePt 0 10.00000000000000000000 2.73604876692571160000 -4.18497755609325670000 +CurvePt 0 10.00000000000000000000 3.53553390593273730000 -3.53553390593273860000 +CurvePt 0 10.00000000000000000000 4.18497755609325580000 -2.73604876692571300000 +CurvePt 0 10.00000000000000000000 4.64894150531215190000 -1.84047354780936500000 +CurvePt 0 10.00000000000000000000 4.91652684208287210000 -0.90981526206072472000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000c 1 1 00000006 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve diff --git a/test/constraint/pt_face_distance/normal_v22.slvs b/test/constraint/pt_face_distance/normal_v22.slvs new file mode 100644 index 0000000..2e4195e --- /dev/null +++ b/test/constraint/pt_face_distance/normal_v22.slvs @@ -0,0 +1,681 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=00020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000003 +Group.type=5100 +Group.order=2 +Group.name=extrude +Group.activeWorkplane.v=00010000 +Group.opA.v=00000002 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.predef.entityB.v=00020000 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00040000 1002 + 2 00040001 1002 + 3 00040020 1002 + 4 00040040 1002 + 5 00040000 1001 + 6 00040001 1001 + 7 00040020 1001 + 8 00040040 1001 + 9 00040001 1003 + 10 80020000 1002 + 11 80020000 1001 + 12 80020001 1002 + 13 80020002 1002 + 14 80020001 1001 + 15 80020002 1001 + 16 80020002 1003 + 17 00000000 1001 + 18 00000000 1002 +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030000 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030001 +AddParam + +Param.h.v.=80030002 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=00020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=00010000 +Request.group.v=00000003 +Request.construction=1 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=00020000 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00010000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030001 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.normal.v=80030003 +Entity.distance.v=80030004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030004 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.normal.v=80030007 +Entity.distance.v=80030008 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030007 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.point[1].v=80030002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.point[1].v=8003000d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actPoint.x=10.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=00020000 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=33 +Constraint.group.v=00000003 +Constraint.workplane.v=00010000 +Constraint.valA=-10.00000000000000000000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=80030012 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Surface 00000001 00646464 80030012 1 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000a 0 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 +TrimBy 00000001 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000004 0 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +TrimBy 00000007 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 +AddSurface +Surface 00000002 00646464 80030011 1 1 +SCtrl 0 0 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000002 1 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000b 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 +TrimBy 00000008 1 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +AddSurface +Surface 00000003 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000002 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000001 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000c 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000003 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddSurface +Surface 00000004 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +TrimBy 00000005 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000004 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000003 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000006 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddSurface +Surface 00000005 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000008 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000007 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000006 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000009 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddSurface +Surface 00000006 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +TrimBy 0000000b 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +TrimBy 0000000a 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000009 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 0000000c 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddSurface +Curve 00000001 1 2 00000001 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 0.00000000000000000000 4.64894150531215188948 1.84047354780936389673 +CurvePt 0 0.00000000000000000000 3.53553390593273775266 3.53553390593273775266 +CurvePt 0 0.00000000000000000000 1.84047354780936411878 4.64894150531215188948 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000002 1 2 00000002 00000003 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 10.00000000000000000000 4.64894150531215188948 1.84047354780936389673 +CurvePt 0 10.00000000000000000000 3.53553390593273775266 3.53553390593273775266 +CurvePt 0 10.00000000000000000000 1.84047354780936411878 4.64894150531215188948 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000003 1 1 00000003 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000004 1 2 00000001 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 0.00000000000000000000 -1.84047354780936323060 4.64894150531215188948 +CurvePt 0 0.00000000000000000000 -3.53553390593273775266 3.53553390593273775266 +CurvePt 0 0.00000000000000000000 -4.64894150531215188948 1.84047354780936434082 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000005 1 2 00000002 00000004 +CCtrl 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 10.00000000000000000000 -1.84047354780936323060 4.64894150531215188948 +CurvePt 0 10.00000000000000000000 -3.53553390593273775266 3.53553390593273775266 +CurvePt 0 10.00000000000000000000 -4.64894150531215188948 1.84047354780936434082 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000006 1 1 00000004 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000007 1 2 00000001 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 0.00000000000000000000 -4.64894150531215188948 -1.84047354780936300855 +CurvePt 0 0.00000000000000000000 -3.53553390593273864084 -3.53553390593273730858 +CurvePt 0 0.00000000000000000000 -1.84047354780936478491 -4.64894150531215188948 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000008 1 2 00000002 00000005 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 10.00000000000000000000 -4.64894150531215188948 -1.84047354780936300855 +CurvePt 0 10.00000000000000000000 -3.53553390593273864084 -3.53553390593273730858 +CurvePt 0 10.00000000000000000000 -1.84047354780936478491 -4.64894150531215188948 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000009 1 1 00000005 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 0000000a 1 2 00000001 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 0.00000000000000000000 1.84047354780936300855 -4.64894150531215188948 +CurvePt 0 0.00000000000000000000 3.53553390593273730858 -3.53553390593273864084 +CurvePt 0 0.00000000000000000000 4.64894150531215188948 -1.84047354780936500696 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000b 1 2 00000002 00000006 +CCtrl 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 10.00000000000000000000 1.84047354780936300855 -4.64894150531215188948 +CurvePt 0 10.00000000000000000000 3.53553390593273730858 -3.53553390593273864084 +CurvePt 0 10.00000000000000000000 4.64894150531215188948 -1.84047354780936500696 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000c 1 1 00000006 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve diff --git a/test/constraint/pt_face_distance/reference.png b/test/constraint/pt_face_distance/reference.png new file mode 100644 index 0000000000000000000000000000000000000000..6e392b3c61c4a9e483479e07a2c677ede3fb41ff GIT binary patch literal 4378 zcmeHLc~FyQ9)9yd1cJz21O=pCWeA5>i6W;0Y9T~W4k6MYC~XngC9psuIl#hd1x682 zL}2ZT2SfrIE=7V!P*6e_2owQBQ4B{o3gHgy7u{h^%`n@Y-Th<7KQi;q_kA<(^Shqk z^W^LyxBV(8Jrn>y<=_EF4*(z^$xm?={N&Ne%UuAd^&WI|I1-aB?8ZjCbT&xp^*+fVIhN2q2cUZ<~(5};l1saYxOG|F?LXf?1@|80B(yb~FN zl)5pB{)S=+*m;Fg@cvP`=F}6na}do}uTLaORq)hz3`ScTT>Z1367sI^KPBW-hrB1d z|M z0})+vWG4Ts{M>wOfrH=m6OAEex;nHWH3^ACWh(w~_w3`{JgPpvfByK(A!k1u(#WrT zVx~Pew#kY7bj{-Uu0U0*v0>Bxn;-DN?y#_A2JM{o%KCOR_v{>^AL6gp1< zQeJaxc00etuDzZl!$Q+juM zXzYv4eg1a_y?YlH=SRKu4L3LtYSOE;_C0IQp2&9bCy!uVr%QsB^Wt63NJ2F63EPgs z^I{=V~`z$l-hD+ChcxV0t{oB#))Uu1P$iZ3m!H|TAGAZ z`?B6%g!BI4N*uJT;KKFr_)|wY*-R8=&({ z?3NaYA}!!{$FC+L%^eU9nW;k`EZvi7dz!T|dPovM8meR|y?YlOp&)rj+x7xp66Jvq zd@^mk`BcMqnjsi6XM(luB@1@<`H90dP~|VqKjL418)WJt#Fs03F==t+rLmZ?S}JvB zqDCkdE{W%-%8Gspvq?;ELx<&=F;6GR1z)yx4JFN&lJu-4r#TBxT;FM&x$a!_pG`{Q zk!*2sR$V%l!D79Bnmdn;8`PWI+?Pu{)-%JwEJktc)|35x{oD*kb6-)Zi`fPXW1@D> z()01r$Hk+)NB*k2MC#KSeo&x-UZ+AooHHUk+K;Od@f)es*!kJ63d2|8n1!_PFifiA z)t=FP?%^@;)}F3coyA{aLv!2a6)tdt+6EW3;uy%!nBl6K*Lb3qu*CMFuXWrK>l;f9 z?I@=hEK96@Ra)~MZOkS(7gs$^P~})T(5K<8b(^eX#MZ@s-j!))re3+}fDs)T=|B*= z=+Keh<3E@G*JTj9+<iaDN@KWNMjTaMh4#lt%)07bYlRz&rY zrfg1)y(>$Z6q+%IZve3B#K+AKYZ}@gO-STp^K31$Kz6y38Tb8ip=ct=ttMfU#48?nnXZoFnnXYh9S%TSEr(*45& z@cy97?xz*3)_Xr9!2T4wPpa0i`Iv%TVuI|clL>@X>V^9y-{9YYvgckY`2ouooZn9n zUTTgHmUt6dj}~mOculUtqUtT~K?r-QJkL$vIine(Q4|aH?`0uD+#SgnS-v~QdfL#3 zVvHT~)3}0~&P6?cz^0Ttb`&+9(VE`4Xyr%Vw zr1gBIY6K)W^Wot9#^9C~v8_%kPZgI1RZn^e8G)z7)_{FwO54ZJv~eO}va3GDW_o9-MxhPzXQO$(mDVD literal 0 HcmV?d00001 diff --git a/test/constraint/pt_face_distance/reference.slvs b/test/constraint/pt_face_distance/reference.slvs new file mode 100644 index 0000000..2d89471 --- /dev/null +++ b/test/constraint/pt_face_distance/reference.slvs @@ -0,0 +1,713 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=00020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000003 +Group.type=5100 +Group.order=2 +Group.name=extrude +Group.activeWorkplane.v=00010000 +Group.opA.v=00000002 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.predef.entityB.v=00020000 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00040000 1002 + 2 00040001 1002 + 3 00040020 1002 + 4 00040040 1002 + 5 00040000 1001 + 6 00040001 1001 + 7 00040020 1001 + 8 00040040 1001 + 9 00040001 1003 + 10 80020000 1002 + 11 80020000 1001 + 12 80020001 1002 + 13 80020002 1002 + 14 80020001 1001 + 15 80020002 1001 + 16 80020002 1003 + 17 00000000 1001 + 18 00000000 1002 +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030000 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030001 +AddParam + +Param.h.v.=80030002 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=00020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=00010000 +Request.group.v=00000003 +Request.construction=1 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=00020000 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=1 +Entity.workplane.v=00010000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030001 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.normal.v=80030003 +Entity.distance.v=80030004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030004 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.normal.v=80030007 +Entity.distance.v=80030008 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030007 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.point[1].v=80030002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=2010 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2010 +Entity.construction=1 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=1 +Entity.point[0].v=8003000f +Entity.point[1].v=8003000d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actPoint.x=10.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=00020000 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=33 +Constraint.group.v=00000003 +Constraint.workplane.v=00010000 +Constraint.valA=-10.00000000000000000000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=80030012 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +AddConstraint + +Surface 00000001 00646464 80030012 1 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000a 0 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 +TrimBy 00000001 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000004 0 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +TrimBy 00000007 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 +AddSurface +Surface 00000002 00646464 80030011 1 1 +SCtrl 0 0 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000002 1 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000b 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 +TrimBy 00000008 1 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +AddSurface +Surface 00000003 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000002 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000001 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000c 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000003 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddSurface +Surface 00000004 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +TrimBy 00000005 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000004 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000003 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000006 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddSurface +Surface 00000005 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000008 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000007 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000006 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000009 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddSurface +Surface 00000006 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +TrimBy 0000000b 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +TrimBy 0000000a 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000009 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 0000000c 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddSurface +Curve 00000001 1 2 00000001 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 0.00000000000000000000 4.91652684208287205081 0.90981526206072360630 +CurvePt 0 0.00000000000000000000 4.64894150531215188948 1.84047354780936389673 +CurvePt 0 0.00000000000000000000 4.18497755609325672310 2.73604876692571252761 +CurvePt 0 0.00000000000000000000 3.53553390593273775266 3.53553390593273775266 +CurvePt 0 0.00000000000000000000 2.73604876692571252761 4.18497755609325672310 +CurvePt 0 0.00000000000000000000 1.84047354780936411878 4.64894150531215188948 +CurvePt 0 0.00000000000000000000 0.90981526206072382834 4.91652684208287205081 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000002 1 2 00000002 00000003 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 10.00000000000000000000 4.91652684208287205081 0.90981526206072360630 +CurvePt 0 10.00000000000000000000 4.64894150531215188948 1.84047354780936389673 +CurvePt 0 10.00000000000000000000 4.18497755609325672310 2.73604876692571252761 +CurvePt 0 10.00000000000000000000 3.53553390593273775266 3.53553390593273775266 +CurvePt 0 10.00000000000000000000 2.73604876692571252761 4.18497755609325672310 +CurvePt 0 10.00000000000000000000 1.84047354780936411878 4.64894150531215188948 +CurvePt 0 10.00000000000000000000 0.90981526206072382834 4.91652684208287205081 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000003 1 1 00000003 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000004 1 2 00000001 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 0.00000000000000000000 -0.90981526206072327323 4.91652684208287205081 +CurvePt 0 0.00000000000000000000 -1.84047354780936323060 4.64894150531215188948 +CurvePt 0 0.00000000000000000000 -2.73604876692571208352 4.18497755609325672310 +CurvePt 0 0.00000000000000000000 -3.53553390593273775266 3.53553390593273775266 +CurvePt 0 0.00000000000000000000 -4.18497755609325672310 2.73604876692571297170 +CurvePt 0 0.00000000000000000000 -4.64894150531215188948 1.84047354780936434082 +CurvePt 0 0.00000000000000000000 -4.91652684208287205081 0.90981526206072405039 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000005 1 2 00000002 00000004 +CCtrl 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 10.00000000000000000000 -0.90981526206072327323 4.91652684208287205081 +CurvePt 0 10.00000000000000000000 -1.84047354780936323060 4.64894150531215188948 +CurvePt 0 10.00000000000000000000 -2.73604876692571208352 4.18497755609325672310 +CurvePt 0 10.00000000000000000000 -3.53553390593273775266 3.53553390593273775266 +CurvePt 0 10.00000000000000000000 -4.18497755609325672310 2.73604876692571297170 +CurvePt 0 10.00000000000000000000 -4.64894150531215188948 1.84047354780936434082 +CurvePt 0 10.00000000000000000000 -4.91652684208287205081 0.90981526206072405039 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000006 1 1 00000004 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000007 1 2 00000001 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 0.00000000000000000000 -4.91652684208287205081 -0.90981526206072305119 +CurvePt 0 0.00000000000000000000 -4.64894150531215188948 -1.84047354780936300855 +CurvePt 0 0.00000000000000000000 -4.18497755609325672310 -2.73604876692571208352 +CurvePt 0 0.00000000000000000000 -3.53553390593273864084 -3.53553390593273730858 +CurvePt 0 0.00000000000000000000 -2.73604876692571297170 -4.18497755609325672310 +CurvePt 0 0.00000000000000000000 -1.84047354780936478491 -4.64894150531215188948 +CurvePt 0 0.00000000000000000000 -0.90981526206072438345 -4.91652684208287205081 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000008 1 2 00000002 00000005 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 10.00000000000000000000 -4.91652684208287205081 -0.90981526206072305119 +CurvePt 0 10.00000000000000000000 -4.64894150531215188948 -1.84047354780936300855 +CurvePt 0 10.00000000000000000000 -4.18497755609325672310 -2.73604876692571208352 +CurvePt 0 10.00000000000000000000 -3.53553390593273864084 -3.53553390593273730858 +CurvePt 0 10.00000000000000000000 -2.73604876692571297170 -4.18497755609325672310 +CurvePt 0 10.00000000000000000000 -1.84047354780936478491 -4.64894150531215188948 +CurvePt 0 10.00000000000000000000 -0.90981526206072438345 -4.91652684208287205081 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000009 1 1 00000005 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 0000000a 1 2 00000001 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 0.00000000000000000000 0.90981526206072282914 -4.91652684208287205081 +CurvePt 0 0.00000000000000000000 1.84047354780936300855 -4.64894150531215188948 +CurvePt 0 0.00000000000000000000 2.73604876692571163943 -4.18497755609325672310 +CurvePt 0 0.00000000000000000000 3.53553390593273730858 -3.53553390593273864084 +CurvePt 0 0.00000000000000000000 4.18497755609325583492 -2.73604876692571297170 +CurvePt 0 0.00000000000000000000 4.64894150531215188948 -1.84047354780936500696 +CurvePt 0 0.00000000000000000000 4.91652684208287205081 -0.90981526206072471652 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000b 1 2 00000002 00000006 +CCtrl 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 10.00000000000000000000 0.90981526206072282914 -4.91652684208287205081 +CurvePt 0 10.00000000000000000000 1.84047354780936300855 -4.64894150531215188948 +CurvePt 0 10.00000000000000000000 2.73604876692571163943 -4.18497755609325672310 +CurvePt 0 10.00000000000000000000 3.53553390593273730858 -3.53553390593273864084 +CurvePt 0 10.00000000000000000000 4.18497755609325583492 -2.73604876692571297170 +CurvePt 0 10.00000000000000000000 4.64894150531215188948 -1.84047354780936500696 +CurvePt 0 10.00000000000000000000 4.91652684208287205081 -0.90981526206072471652 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000c 1 1 00000006 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve diff --git a/test/constraint/pt_face_distance/reference_v20.slvs b/test/constraint/pt_face_distance/reference_v20.slvs new file mode 100644 index 0000000..c506e9b --- /dev/null +++ b/test/constraint/pt_face_distance/reference_v20.slvs @@ -0,0 +1,712 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=00020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000003 +Group.type=5100 +Group.order=2 +Group.name=extrude +Group.activeWorkplane.v=00010000 +Group.opA.v=00000002 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.predef.entityB.v=00020000 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00040000 1002 + 2 00040001 1002 + 3 00040020 1002 + 4 00040040 1002 + 5 00040000 1001 + 6 00040001 1001 + 7 00040020 1001 + 8 00040040 1001 + 9 00040001 1003 + 10 80020000 1002 + 11 80020000 1001 + 12 80020001 1002 + 13 80020002 1002 + 14 80020001 1001 + 15 80020002 1001 + 16 80020002 1003 + 17 00000000 1001 + 18 00000000 1002 +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030000 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030001 +AddParam + +Param.h.v.=80030002 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=00020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=00010000 +Request.group.v=00000003 +Request.construction=1 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=00020000 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00010000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030001 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.normal.v=80030003 +Entity.distance.v=80030004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030004 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.normal.v=80030007 +Entity.distance.v=80030008 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030007 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.point[1].v=80030002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.point[1].v=8003000d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actPoint.x=10.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=00020000 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=33 +Constraint.group.v=00000003 +Constraint.workplane.v=00010000 +Constraint.valA=-10.00000000000000000000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=80030012 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +AddConstraint + +Surface 00000001 00646464 80030012 1 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000a 0 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 +TrimBy 00000001 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000004 0 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +TrimBy 00000007 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 +AddSurface +Surface 00000002 00646464 80030011 1 1 +SCtrl 0 0 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000002 1 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000b 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 +TrimBy 00000008 1 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +AddSurface +Surface 00000003 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 2 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000002 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000003 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000001 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000c 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000004 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -4.99999999999999910000 5.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 1 1 10.00000000000000000000 -4.99999999999999910000 5.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 2 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +TrimBy 00000005 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000006 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000004 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000003 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddSurface +Surface 00000005 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -5.00000000000000090000 -4.99999999999999910000 Weight 0.70710678118654757000 +SCtrl 1 1 10.00000000000000000000 -5.00000000000000090000 -4.99999999999999910000 Weight 0.70710678118654757000 +SCtrl 2 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000008 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000009 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000007 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000006 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddSurface +Surface 00000006 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 4.99999999999999910000 -5.00000000000000090000 Weight 0.70710678118654757000 +SCtrl 1 1 10.00000000000000000000 4.99999999999999910000 -5.00000000000000090000 Weight 0.70710678118654757000 +SCtrl 2 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +TrimBy 0000000b 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +TrimBy 0000000c 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +TrimBy 0000000a 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000009 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddSurface +Curve 00000001 1 2 00000001 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 0.00000000000000000000 4.91652684208287210000 0.90981526206072361000 +CurvePt 0 0.00000000000000000000 4.64894150531215190000 1.84047354780936390000 +CurvePt 0 0.00000000000000000000 4.18497755609325670000 2.73604876692571250000 +CurvePt 0 0.00000000000000000000 3.53553390593273780000 3.53553390593273780000 +CurvePt 0 0.00000000000000000000 2.73604876692571250000 4.18497755609325670000 +CurvePt 0 0.00000000000000000000 1.84047354780936410000 4.64894150531215190000 +CurvePt 0 0.00000000000000000000 0.90981526206072383000 4.91652684208287210000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000002 1 2 00000002 00000003 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 10.00000000000000000000 4.91652684208287210000 0.90981526206072361000 +CurvePt 0 10.00000000000000000000 4.64894150531215190000 1.84047354780936390000 +CurvePt 0 10.00000000000000000000 4.18497755609325670000 2.73604876692571250000 +CurvePt 0 10.00000000000000000000 3.53553390593273780000 3.53553390593273780000 +CurvePt 0 10.00000000000000000000 2.73604876692571250000 4.18497755609325670000 +CurvePt 0 10.00000000000000000000 1.84047354780936410000 4.64894150531215190000 +CurvePt 0 10.00000000000000000000 0.90981526206072383000 4.91652684208287210000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000003 1 1 00000003 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000004 1 2 00000001 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -4.99999999999999910000 5.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 0.00000000000000000000 -0.90981526206072327000 4.91652684208287210000 +CurvePt 0 0.00000000000000000000 -1.84047354780936320000 4.64894150531215190000 +CurvePt 0 0.00000000000000000000 -2.73604876692571210000 4.18497755609325670000 +CurvePt 0 0.00000000000000000000 -3.53553390593273780000 3.53553390593273780000 +CurvePt 0 0.00000000000000000000 -4.18497755609325670000 2.73604876692571300000 +CurvePt 0 0.00000000000000000000 -4.64894150531215190000 1.84047354780936430000 +CurvePt 0 0.00000000000000000000 -4.91652684208287210000 0.90981526206072405000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000005 1 2 00000002 00000004 +CCtrl 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -4.99999999999999910000 5.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 10.00000000000000000000 -0.90981526206072327000 4.91652684208287210000 +CurvePt 0 10.00000000000000000000 -1.84047354780936320000 4.64894150531215190000 +CurvePt 0 10.00000000000000000000 -2.73604876692571210000 4.18497755609325670000 +CurvePt 0 10.00000000000000000000 -3.53553390593273780000 3.53553390593273780000 +CurvePt 0 10.00000000000000000000 -4.18497755609325670000 2.73604876692571300000 +CurvePt 0 10.00000000000000000000 -4.64894150531215190000 1.84047354780936430000 +CurvePt 0 10.00000000000000000000 -4.91652684208287210000 0.90981526206072405000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000006 1 1 00000004 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000007 1 2 00000001 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -5.00000000000000090000 -4.99999999999999910000 Weight 0.70710678118654757000 +CCtrl 2 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 0.00000000000000000000 -4.91652684208287210000 -0.90981526206072305000 +CurvePt 0 0.00000000000000000000 -4.64894150531215190000 -1.84047354780936300000 +CurvePt 0 0.00000000000000000000 -4.18497755609325670000 -2.73604876692571210000 +CurvePt 0 0.00000000000000000000 -3.53553390593273860000 -3.53553390593273730000 +CurvePt 0 0.00000000000000000000 -2.73604876692571300000 -4.18497755609325670000 +CurvePt 0 0.00000000000000000000 -1.84047354780936480000 -4.64894150531215190000 +CurvePt 0 0.00000000000000000000 -0.90981526206072438000 -4.91652684208287210000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000008 1 2 00000002 00000005 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000090000 -4.99999999999999910000 Weight 0.70710678118654757000 +CCtrl 2 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 10.00000000000000000000 -4.91652684208287210000 -0.90981526206072305000 +CurvePt 0 10.00000000000000000000 -4.64894150531215190000 -1.84047354780936300000 +CurvePt 0 10.00000000000000000000 -4.18497755609325670000 -2.73604876692571210000 +CurvePt 0 10.00000000000000000000 -3.53553390593273860000 -3.53553390593273730000 +CurvePt 0 10.00000000000000000000 -2.73604876692571300000 -4.18497755609325670000 +CurvePt 0 10.00000000000000000000 -1.84047354780936480000 -4.64894150531215190000 +CurvePt 0 10.00000000000000000000 -0.90981526206072438000 -4.91652684208287210000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000009 1 1 00000005 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 0000000a 1 2 00000001 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 4.99999999999999910000 -5.00000000000000090000 Weight 0.70710678118654757000 +CCtrl 2 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 0.00000000000000000000 0.90981526206072283000 -4.91652684208287210000 +CurvePt 0 0.00000000000000000000 1.84047354780936300000 -4.64894150531215190000 +CurvePt 0 0.00000000000000000000 2.73604876692571160000 -4.18497755609325670000 +CurvePt 0 0.00000000000000000000 3.53553390593273730000 -3.53553390593273860000 +CurvePt 0 0.00000000000000000000 4.18497755609325580000 -2.73604876692571300000 +CurvePt 0 0.00000000000000000000 4.64894150531215190000 -1.84047354780936500000 +CurvePt 0 0.00000000000000000000 4.91652684208287210000 -0.90981526206072472000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000b 1 2 00000002 00000006 +CCtrl 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 4.99999999999999910000 -5.00000000000000090000 Weight 0.70710678118654757000 +CCtrl 2 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 10.00000000000000000000 0.90981526206072283000 -4.91652684208287210000 +CurvePt 0 10.00000000000000000000 1.84047354780936300000 -4.64894150531215190000 +CurvePt 0 10.00000000000000000000 2.73604876692571160000 -4.18497755609325670000 +CurvePt 0 10.00000000000000000000 3.53553390593273730000 -3.53553390593273860000 +CurvePt 0 10.00000000000000000000 4.18497755609325580000 -2.73604876692571300000 +CurvePt 0 10.00000000000000000000 4.64894150531215190000 -1.84047354780936500000 +CurvePt 0 10.00000000000000000000 4.91652684208287210000 -0.90981526206072472000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000c 1 1 00000006 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve diff --git a/test/constraint/pt_face_distance/reference_v22.slvs b/test/constraint/pt_face_distance/reference_v22.slvs new file mode 100644 index 0000000..a57b808 --- /dev/null +++ b/test/constraint/pt_face_distance/reference_v22.slvs @@ -0,0 +1,681 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=00020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000003 +Group.type=5100 +Group.order=2 +Group.name=extrude +Group.activeWorkplane.v=00010000 +Group.opA.v=00000002 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.predef.entityB.v=00020000 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00040000 1002 + 2 00040001 1002 + 3 00040020 1002 + 4 00040040 1002 + 5 00040000 1001 + 6 00040001 1001 + 7 00040020 1001 + 8 00040040 1001 + 9 00040001 1003 + 10 80020000 1002 + 11 80020000 1001 + 12 80020001 1002 + 13 80020002 1002 + 14 80020001 1001 + 15 80020002 1001 + 16 80020002 1003 + 17 00000000 1001 + 18 00000000 1002 +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030000 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030001 +AddParam + +Param.h.v.=80030002 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=00020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=00010000 +Request.group.v=00000003 +Request.construction=1 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=00020000 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=00020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=00010000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030001 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.normal.v=80030003 +Entity.distance.v=80030004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030004 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.normal.v=80030007 +Entity.distance.v=80030008 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030007 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.point[1].v=80030002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.point[1].v=8003000d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actPoint.x=10.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=00020000 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=33 +Constraint.group.v=00000003 +Constraint.workplane.v=00010000 +Constraint.valA=-10.00000000000000000000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=80030012 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +AddConstraint + +Surface 00000001 00646464 80030012 1 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000a 0 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 +TrimBy 00000001 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000004 0 0.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +TrimBy 00000007 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000000000 +AddSurface +Surface 00000002 00646464 80030011 1 1 +SCtrl 0 0 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000002 1 10.00000000000000000000 0.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000b 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 +TrimBy 00000008 1 10.00000000000000000000 -0.00000000000000044409 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000044409 +AddSurface +Surface 00000003 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000002 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000001 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000c 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000003 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddSurface +Surface 00000004 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +TrimBy 00000005 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000004 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000003 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +TrimBy 00000006 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddSurface +Surface 00000005 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000008 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000007 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000006 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +TrimBy 00000009 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddSurface +Surface 00000006 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 0.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +SCtrl 1 1 10.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +SCtrl 2 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +TrimBy 0000000b 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +TrimBy 0000000a 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 00000009 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +TrimBy 0000000c 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddSurface +Curve 00000001 1 2 00000001 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 0.00000000000000000000 4.64894150531215188948 1.84047354780936389673 +CurvePt 0 0.00000000000000000000 3.53553390593273775266 3.53553390593273775266 +CurvePt 0 0.00000000000000000000 1.84047354780936411878 4.64894150531215188948 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000002 1 2 00000002 00000003 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 10.00000000000000000000 4.64894150531215188948 1.84047354780936389673 +CurvePt 0 10.00000000000000000000 3.53553390593273775266 3.53553390593273775266 +CurvePt 0 10.00000000000000000000 1.84047354780936411878 4.64894150531215188948 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000003 1 1 00000003 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +AddCurve +Curve 00000004 1 2 00000001 00000004 +CCtrl 0 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 0.00000000000000000000 -1.84047354780936323060 4.64894150531215188948 +CurvePt 0 0.00000000000000000000 -3.53553390593273775266 3.53553390593273775266 +CurvePt 0 0.00000000000000000000 -4.64894150531215188948 1.84047354780936434082 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000005 1 2 00000002 00000004 +CCtrl 0 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -4.99999999999999911182 5.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 +CurvePt 0 10.00000000000000000000 -1.84047354780936323060 4.64894150531215188948 +CurvePt 0 10.00000000000000000000 -3.53553390593273775266 3.53553390593273775266 +CurvePt 0 10.00000000000000000000 -4.64894150531215188948 1.84047354780936434082 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000006 1 1 00000004 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +AddCurve +Curve 00000007 1 2 00000001 00000005 +CCtrl 0 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 0.00000000000000000000 -4.64894150531215188948 -1.84047354780936300855 +CurvePt 0 0.00000000000000000000 -3.53553390593273864084 -3.53553390593273730858 +CurvePt 0 0.00000000000000000000 -1.84047354780936478491 -4.64894150531215188948 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000008 1 2 00000002 00000005 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000088818 -4.99999999999999911182 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 +CurvePt 0 10.00000000000000000000 -4.64894150531215188948 -1.84047354780936300855 +CurvePt 0 10.00000000000000000000 -3.53553390593273864084 -3.53553390593273730858 +CurvePt 0 10.00000000000000000000 -1.84047354780936478491 -4.64894150531215188948 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 00000009 1 1 00000005 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +AddCurve +Curve 0000000a 1 2 00000001 00000006 +CCtrl 0 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 0.00000000000000000000 1.84047354780936300855 -4.64894150531215188948 +CurvePt 0 0.00000000000000000000 3.53553390593273730858 -3.53553390593273864084 +CurvePt 0 0.00000000000000000000 4.64894150531215188948 -1.84047354780936500696 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000b 1 2 00000002 00000006 +CCtrl 0 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 4.99999999999999911182 -5.00000000000000088818 Weight 0.70710678118654757274 +CCtrl 2 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 +CurvePt 0 10.00000000000000000000 1.84047354780936300855 -4.64894150531215188948 +CurvePt 0 10.00000000000000000000 3.53553390593273730858 -3.53553390593273864084 +CurvePt 0 10.00000000000000000000 4.64894150531215188948 -1.84047354780936500696 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve +Curve 0000000c 1 1 00000006 00000003 +CCtrl 0 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 +AddCurve diff --git a/test/constraint/pt_face_distance/test.cpp b/test/constraint/pt_face_distance/test.cpp new file mode 100644 index 0000000..7e2a06e --- /dev/null +++ b/test/constraint/pt_face_distance/test.cpp @@ -0,0 +1,33 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(reference_roundtrip) { + CHECK_LOAD("reference.slvs"); + CHECK_RENDER("reference.png"); + CHECK_SAVE("reference.slvs"); +} + +TEST_CASE(reference_migrate_from_v20) { + CHECK_LOAD("reference_v20.slvs"); + CHECK_SAVE("reference.slvs"); +} + +TEST_CASE(reference_migrate_from_v22) { + CHECK_LOAD("reference_v22.slvs"); + CHECK_SAVE("reference.slvs"); +} diff --git a/test/constraint/pt_in_plane/normal.png b/test/constraint/pt_in_plane/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..b770451f3a56df7288f51e1c10d35646b2135cb7 GIT binary patch literal 4228 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%0Dr5 zm#d|7B>i+ik;R>5^B=8*)|0^n8d%l)o@FuKyMh?vtn87Wh!NCItWP@r zuZnhhjMzLl^kQNA3kR@&b7}2xQgUza-lg6ieM)^{CY$izFXAMqv^;pYk0QpFB7eld NCYLQ6ve%Oj{R_kdMsffE literal 0 HcmV?d00001 diff --git a/test/constraint/pt_line_distance/extended.slvs b/test/constraint/pt_line_distance/extended.slvs new file mode 100644 index 0000000..c73570d --- /dev/null +++ b/test/constraint/pt_line_distance/extended.slvs @@ -0,0 +1,316 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=32 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=-5.00000000000000000000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.x=-10.00000000000000000000 +Constraint.disp.offset.y=0.0000000000000000000 +AddConstraint + diff --git a/test/constraint/pt_line_distance/free_in_3d.png b/test/constraint/pt_line_distance/free_in_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..db351dfde01e033f02d4a60e598aa85385f99d74 GIT binary patch literal 4403 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 literal 0 HcmV?d00001 diff --git a/test/constraint/pt_on_circle/normal.slvs b/test/constraint/pt_on_circle/normal.slvs new file mode 100644 index 0000000..462e0cd --- /dev/null +++ b/test/constraint/pt_on_circle/normal.slvs @@ -0,0 +1,318 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-9.99999912024158099655 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=4.99999824048316199310 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000087975841900345 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-9.99999912024158099655 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=4.99999824048316199310 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000087975841900345 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=100 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/pt_on_circle/normal_v20.slvs b/test/constraint/pt_on_circle/normal_v20.slvs new file mode 100644 index 0000000..d36fc3b --- /dev/null +++ b/test/constraint/pt_on_circle/normal_v20.slvs @@ -0,0 +1,318 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-9.99999912024158100000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=4.99999824048316200000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000087975841900000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-9.99999912024158100000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=4.99999824048316200000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000087975841900000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=100 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/pt_on_circle/normal_v22.slvs b/test/constraint/pt_on_circle/normal_v22.slvs new file mode 100644 index 0000000..fea01e6 --- /dev/null +++ b/test/constraint/pt_on_circle/normal_v22.slvs @@ -0,0 +1,318 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-9.99999912024158100000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=4.99999824048316200000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000087975841900000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-9.99999912024158100000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=4.99999824048316200000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000087975841900000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=100 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/pt_on_circle/test.cpp b/test/constraint/pt_on_circle/test.cpp new file mode 100644 index 0000000..ce38ba5 --- /dev/null +++ b/test/constraint/pt_on_circle/test.cpp @@ -0,0 +1,23 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(negative_dia) { + CHECK_LOAD("negative_dia.slvs"); + Entity *e = SK.GetEntity(hRequest { 4 }.entity(0)); + CHECK_TRUE(e->CircleGetRadiusNum() > 0); +} diff --git a/test/constraint/pt_on_face/normal.png b/test/constraint/pt_on_face/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..0d6ef8215d12540e19517c40006ac4f53ed8239f GIT binary patch literal 4662 zcmeHLdpMM78-M0CqhTnA9KLEPV%top&9V$85!Pv2Vw^`}TeK6tXh!i;tx#-bQ9?)O zVQP0A7ca^r6^db1%&xC>n1pqhgz>%7woT1;eb-*s_kI8DT<=`(T=Txq^W69Eci;E( zyPt&59j#@hS4jf^WNmFMzW@NDEPnBl=s&fA8IJ&vpSQK#=5p}-$m4Bc4{gqU^Rs2V zUi<1Hh462rO9x~W2X!3$pSAH5jko(+^xWBOl(ed9ug4|wkY@Dy-{=~uJIey}t`Mu-CROZ= z^OUqfFTcn_3pU$f;^uwfQ~QLc$)B)Nw5tppnTEnA!Z8blx6lU*kW(2VUcF5_+5Fc^1`ZU7dVvDF-^(h>5GMlWf!HU} zp8#arS#dMAr34iCVA?V-O?)E)Zb%R_t55|4_OBPqUMxoH8n^ad z2a$-ScvCBLWWoN=#m+eC?_THzCUXaWTqgo9)yoz5d|Mk^K zJ=EP$N1N-32`Q7NYQ7HLVqI2ZNdhz*X`Iy?JvWg<6OxeL?jyKV+neoCoU_`7d~6V& zNOc~)*)}mT5nm^mD({+}NT7Zn3M1y3Xmt30OP z(UcOfY49b6_w@k&r!B?YN4e{sklMH0 zh0GeuZ)2W?(FK#ed9LN>%?%@ymYHp*Vv0T(I?Y?v+S+PWdNcj+boa^>*+@mpL7%Cb(x8cN{ z$-?9=wI;Owk5O|e`f9PbBjY#Kc;l!Q4O)c^p$v7+Qasg@z0`{P+40x#L|C@c9`BE? z4tzT>Wm-!Ki0pXU7#E5ssvgyQmLhn_51vwhv*ajXT@1;T-RI+bO!;XuDV@YbApDE| zp&{$NHJr@lyyuHym=RdtUAs#U94zk5(<)u4!ly0>XE;s^GcK%4Q@Wb`l)S^GO1BC# zsY>38IOFMZ;l0)A=`s%AQ`ew-a0r4WG<^}3Fs9>N9MG&jd)eQ(wm(bNv{*b|?pMP?A4<(;HuSU}!YjCugTEWq zzK)6?s;1FGmI)qVWdF%;gHdPss* zz%ZY4M^sUkZfI?U_bqaA+*36PiQ87O703D@7RQo5Md(Kxy~=2~gFwVtt^WJw%Z7KYiV_^wSf0ONkz{#hp$n-F%`2OOV`i z2^yoIZv-~{W;D>{dZ!@+UH-O;kTbEWu>$>hZ>3Iaur#_*H@)G$og8Ui7&WEf`f*KM z)~jeVTK8?IT``oR#e%u#^lezzJ}^S-b!57y!*%3~j;{~UTnb}ZMG+)zG@1BWwn3$h&Z z)cx-B1Gf9&)-Tqid)w~&@0HTw$q(zwE2AN+Ht^#5&)ldVp84e{joD~Z$d8^_7O~4R z3%CcKCT!>&#ZSm~i^cSV$6&5geyo3|ph_YM1yIv3>gois}#W!>;5*W$Rh+-5MoN zW&lmFHXr^BB5XLIb5ysV-`huM^9bJO{#iEn_dUtMXoRBM-5bDu!sa@5CIMii zAb!Z7cY1L2o$x7XxOe3nebs6`mLsZmKyryLYop+c<3zxVZOTL!B)!`;iUXu)Z~0?V zOqui0MgU8E{Qr*7Oyj>|ib;6T<;NcCAlaHalG+w$_ynsSLcTPxCeG76Nyq%C#i+r_&B zb>zh2ofgzPQl~RuE)$9a^I_`6S(U2^6#{ORGcKYQ$yQ2V@?0QvvIt6OQEj2FFyD-4yS_;C@J8h{5+Ac;5 zsNS3|=ITTUgyi=jjFejtNSyiyWz^gFpP$Nu3YQK^=h=y9*@xfPNmry#NTMcxi1yMZ zt|s*_Zn+7s4nIeQ$*arviY}VH%WrF0{7+9r{^S5=)?B@N$U}>`We0j51Z=GwElVt@ GasLL6s(|+Z literal 0 HcmV?d00001 diff --git a/test/constraint/pt_on_face/normal.slvs b/test/constraint/pt_on_face/normal.slvs new file mode 100644 index 0000000..7656071 --- /dev/null +++ b/test/constraint/pt_on_face/normal.slvs @@ -0,0 +1,703 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000003 +Group.type=5100 +Group.order=2 +Group.name=extrude +Group.activeWorkplane.v=00010000 +Group.opA.v=00000002 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.predef.entityB.v=80020000 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00040000 1002 + 2 00040001 1002 + 3 00040020 1002 + 4 00040040 1002 + 5 00040000 1001 + 6 00040001 1001 + 7 00040020 1001 + 8 00040040 1001 + 9 00040001 1003 + 10 80020000 1002 + 11 80020000 1001 + 12 80020001 1002 + 13 80020002 1002 + 14 80020001 1001 + 15 80020002 1001 + 16 80020002 1003 + 17 00000000 1001 + 18 00000000 1002 +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=80030000 +AddParam + +Param.h.v.=80030001 +AddParam + +Param.h.v.=80030002 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000003 +Request.construction=1 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=1 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030001 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.normal.v=80030003 +Entity.distance.v=80030004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030004 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.normal.v=80030007 +Entity.distance.v=80030008 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.z=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030007 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.point[1].v=80030002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=2010 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2010 +Entity.construction=1 +Entity.actPoint.z=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=1 +Entity.point[0].v=8003000f +Entity.point[1].v=8003000d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actPoint.z=-10.00000000000000000000 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=43 +Constraint.group.v=00000003 +Constraint.ptA.v=00050000 +Constraint.entityA.v=80030012 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Surface 00000001 00646464 80030011 1 1 +SCtrl 0 0 -5.00000000000000000000 -5.00000000000000088818 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000010000000827 -5.00000000000000088818 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000000000 4.99999999999999911182 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 5.00000000010000000827 4.99999999999999911182 -10.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000a 0 -0.00000000000000177636 -5.00000000000000088818 -10.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 -10.00000000000000000000 +TrimBy 00000001 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 0.00000000000000000000 4.99999999999999911182 -10.00000000000000000000 +TrimBy 00000004 0 0.00000000000000000000 4.99999999999999911182 -10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 +TrimBy 00000007 0 -5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 -0.00000000000000177636 -5.00000000000000088818 -10.00000000000000000000 +AddSurface +Surface 00000002 00646464 80030012 1 1 +SCtrl 0 0 5.00000000010000000827 -5.00000000000000088818 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 -5.00000000000000088818 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000010000000827 4.99999999999999911182 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 -5.00000000000000000000 4.99999999999999911182 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 1 -5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000000000 4.99999999999999911182 0.00000000000000000000 +TrimBy 00000002 1 0.00000000000000000000 4.99999999999999911182 0.00000000000000000000 5.00000000000000088818 -0.00000000000000000000 0.00000000000000000000 +TrimBy 0000000b 1 5.00000000000000088818 -0.00000000000000088818 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000088818 0.00000000000000000000 +TrimBy 00000008 1 -0.00000000000000088818 -5.00000000000000088818 0.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000003 00646464 00000000 2 1 +SCtrl 0 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000000000000000 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000002 0 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000001 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 +TrimBy 0000000c 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 +TrimBy 00000003 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +AddSurface +Surface 00000004 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -4.99999999999999911182 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 -4.99999999999999911182 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 0 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +TrimBy 00000004 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +TrimBy 00000003 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000006 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +AddSurface +Surface 00000005 00646464 00000000 2 1 +SCtrl 0 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000088818 -4.99999999999999911182 -10.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 -5.00000000000000088818 -4.99999999999999911182 0.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000008 0 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +TrimBy 00000007 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +TrimBy 00000006 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +TrimBy 00000009 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +AddSurface +Surface 00000006 00646464 00000000 2 1 +SCtrl 0 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 4.99999999999999911182 -5.00000000000000088818 -10.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 4.99999999999999911182 -5.00000000000000088818 0.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000b 0 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 +TrimBy 0000000a 1 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +TrimBy 00000009 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000c 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 +AddSurface +Curve 00000001 1 2 00000001 00000003 +CCtrl 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 +CurvePt 0 4.91652684208287205081 0.90981526206072360630 -10.00000000000000000000 +CurvePt 0 4.64894150531215188948 1.84047354780936389673 -10.00000000000000000000 +CurvePt 0 4.18497755609325672310 2.73604876692571252761 -10.00000000000000000000 +CurvePt 0 3.53553390593273775266 3.53553390593273775266 -10.00000000000000000000 +CurvePt 0 2.73604876692571252761 4.18497755609325672310 -10.00000000000000000000 +CurvePt 0 1.84047354780936411878 4.64894150531215188948 -10.00000000000000000000 +CurvePt 0 0.90981526206072382834 4.91652684208287205081 -10.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +AddCurve +Curve 00000002 1 2 00000002 00000003 +CCtrl 0 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 +CurvePt 0 4.91652684208287205081 0.90981526206072360630 0.00000000000000000000 +CurvePt 0 4.64894150531215188948 1.84047354780936389673 0.00000000000000000000 +CurvePt 0 4.18497755609325672310 2.73604876692571252761 0.00000000000000000000 +CurvePt 0 3.53553390593273775266 3.53553390593273775266 0.00000000000000000000 +CurvePt 0 2.73604876692571252761 4.18497755609325672310 0.00000000000000000000 +CurvePt 0 1.84047354780936411878 4.64894150531215188948 0.00000000000000000000 +CurvePt 0 0.90981526206072382834 4.91652684208287205081 0.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000003 1 1 00000003 00000004 +CCtrl 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000004 1 2 00000001 00000004 +CCtrl 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -4.99999999999999911182 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +CurvePt 0 -0.90981526206072327323 4.91652684208287205081 -10.00000000000000000000 +CurvePt 0 -1.84047354780936323060 4.64894150531215188948 -10.00000000000000000000 +CurvePt 0 -2.73604876692571208352 4.18497755609325672310 -10.00000000000000000000 +CurvePt 0 -3.53553390593273775266 3.53553390593273775266 -10.00000000000000000000 +CurvePt 0 -4.18497755609325672310 2.73604876692571297170 -10.00000000000000000000 +CurvePt 0 -4.64894150531215188948 1.84047354780936434082 -10.00000000000000000000 +CurvePt 0 -4.91652684208287205081 0.90981526206072405039 -10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +AddCurve +Curve 00000005 1 2 00000002 00000004 +CCtrl 0 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -4.99999999999999911182 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 -0.90981526206072327323 4.91652684208287205081 0.00000000000000000000 +CurvePt 0 -1.84047354780936323060 4.64894150531215188948 0.00000000000000000000 +CurvePt 0 -2.73604876692571208352 4.18497755609325672310 0.00000000000000000000 +CurvePt 0 -3.53553390593273775266 3.53553390593273775266 0.00000000000000000000 +CurvePt 0 -4.18497755609325672310 2.73604876692571297170 0.00000000000000000000 +CurvePt 0 -4.64894150531215188948 1.84047354780936434082 0.00000000000000000000 +CurvePt 0 -4.91652684208287205081 0.90981526206072405039 0.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +AddCurve +Curve 00000006 1 1 00000004 00000005 +CCtrl 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +AddCurve +Curve 00000007 1 2 00000001 00000005 +CCtrl 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000088818 -4.99999999999999911182 -10.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +CurvePt 0 -4.91652684208287205081 -0.90981526206072305119 -10.00000000000000000000 +CurvePt 0 -4.64894150531215188948 -1.84047354780936300855 -10.00000000000000000000 +CurvePt 0 -4.18497755609325672310 -2.73604876692571208352 -10.00000000000000000000 +CurvePt 0 -3.53553390593273864084 -3.53553390593273730858 -10.00000000000000000000 +CurvePt 0 -2.73604876692571297170 -4.18497755609325672310 -10.00000000000000000000 +CurvePt 0 -1.84047354780936478491 -4.64894150531215188948 -10.00000000000000000000 +CurvePt 0 -0.90981526206072438345 -4.91652684208287205081 -10.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +AddCurve +Curve 00000008 1 2 00000002 00000005 +CCtrl 0 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000088818 -4.99999999999999911182 0.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +CurvePt 0 -4.91652684208287205081 -0.90981526206072305119 0.00000000000000000000 +CurvePt 0 -4.64894150531215188948 -1.84047354780936300855 0.00000000000000000000 +CurvePt 0 -4.18497755609325672310 -2.73604876692571208352 0.00000000000000000000 +CurvePt 0 -3.53553390593273864084 -3.53553390593273730858 0.00000000000000000000 +CurvePt 0 -2.73604876692571297170 -4.18497755609325672310 0.00000000000000000000 +CurvePt 0 -1.84047354780936478491 -4.64894150531215188948 0.00000000000000000000 +CurvePt 0 -0.90981526206072438345 -4.91652684208287205081 0.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000009 1 1 00000005 00000006 +CCtrl 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 0000000a 1 2 00000001 00000006 +CCtrl 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 4.99999999999999911182 -5.00000000000000088818 -10.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +CurvePt 0 0.90981526206072282914 -4.91652684208287205081 -10.00000000000000000000 +CurvePt 0 1.84047354780936300855 -4.64894150531215188948 -10.00000000000000000000 +CurvePt 0 2.73604876692571163943 -4.18497755609325672310 -10.00000000000000000000 +CurvePt 0 3.53553390593273730858 -3.53553390593273864084 -10.00000000000000000000 +CurvePt 0 4.18497755609325583492 -2.73604876692571297170 -10.00000000000000000000 +CurvePt 0 4.64894150531215188948 -1.84047354780936500696 -10.00000000000000000000 +CurvePt 0 4.91652684208287205081 -0.90981526206072471652 -10.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 +AddCurve +Curve 0000000b 1 2 00000002 00000006 +CCtrl 0 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 4.99999999999999911182 -5.00000000000000088818 0.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +CurvePt 0 0.90981526206072282914 -4.91652684208287205081 0.00000000000000000000 +CurvePt 0 1.84047354780936300855 -4.64894150531215188948 0.00000000000000000000 +CurvePt 0 2.73604876692571163943 -4.18497755609325672310 0.00000000000000000000 +CurvePt 0 3.53553390593273730858 -3.53553390593273864084 0.00000000000000000000 +CurvePt 0 4.18497755609325583492 -2.73604876692571297170 0.00000000000000000000 +CurvePt 0 4.64894150531215188948 -1.84047354780936500696 0.00000000000000000000 +CurvePt 0 4.91652684208287205081 -0.90981526206072471652 0.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 +AddCurve +Curve 0000000c 1 1 00000006 00000003 +CCtrl 0 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 +AddCurve diff --git a/test/constraint/pt_on_face/normal_v20.slvs b/test/constraint/pt_on_face/normal_v20.slvs new file mode 100644 index 0000000..e3c7212 --- /dev/null +++ b/test/constraint/pt_on_face/normal_v20.slvs @@ -0,0 +1,670 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000003 +Group.type=5100 +Group.order=2 +Group.name=extrude +Group.activeWorkplane.v=00010000 +Group.opA.v=00000002 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.predef.entityB.v=80020000 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00040000 1002 + 2 00040001 1002 + 3 00040020 1002 + 4 00040040 1002 + 5 00040000 1001 + 6 00040001 1001 + 7 00040020 1001 + 8 00040040 1001 + 9 00040001 1003 + 10 80020000 1002 + 11 80020000 1001 + 12 80020001 1002 + 13 80020002 1002 + 14 80020001 1001 + 15 80020002 1001 + 16 80020002 1003 + 17 00000000 1001 + 18 00000000 1002 +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=80030000 +AddParam + +Param.h.v.=80030001 +AddParam + +Param.h.v.=80030002 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000003 +Request.construction=1 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030001 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.normal.v=80030003 +Entity.distance.v=80030004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030004 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.normal.v=80030007 +Entity.distance.v=80030008 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.z=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030007 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.point[1].v=80030002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.z=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.point[1].v=8003000d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actPoint.z=-10.00000000000000000000 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=43 +Constraint.group.v=00000003 +Constraint.ptA.v=00050000 +Constraint.entityA.v=80030012 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Surface 00000001 00646464 80030011 1 1 +SCtrl 0 0 -5.00000000000000000000 -5.00000000000000090000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000010000000000 -5.00000000000000090000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000000000 4.99999999999999910000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 5.00000000010000000000 4.99999999999999910000 -10.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000a 0 -0.00000000000000177636 -5.00000000000000090000 -10.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 -10.00000000000000000000 +TrimBy 00000001 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 0.00000000000000000000 4.99999999999999910000 -10.00000000000000000000 +TrimBy 00000004 0 0.00000000000000000000 4.99999999999999910000 -10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 +TrimBy 00000007 0 -5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 -0.00000000000000177636 -5.00000000000000090000 -10.00000000000000000000 +AddSurface +Surface 00000002 00646464 80030012 1 1 +SCtrl 0 0 5.00000000010000000000 -5.00000000000000090000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 -5.00000000000000090000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000010000000000 4.99999999999999910000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 -5.00000000000000000000 4.99999999999999910000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 1 -5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000000000 4.99999999999999910000 0.00000000000000000000 +TrimBy 00000002 1 0.00000000000000000000 4.99999999999999910000 0.00000000000000000000 5.00000000000000090000 -0.00000000000000000000 0.00000000000000000000 +TrimBy 0000000b 1 5.00000000000000090000 -0.00000000000000088818 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000090000 0.00000000000000000000 +TrimBy 00000008 1 -0.00000000000000088818 -5.00000000000000090000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000003 00646464 00000000 2 1 +SCtrl 0 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000000000000000 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 1 1 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 2 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000002 0 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000003 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +TrimBy 00000001 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 +TrimBy 0000000c 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000004 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -4.99999999999999910000 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 1 1 -4.99999999999999910000 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 2 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 0 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +TrimBy 00000006 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +TrimBy 00000004 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +TrimBy 00000003 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000005 00646464 00000000 2 1 +SCtrl 0 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000090000 -4.99999999999999910000 -10.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 1 1 -5.00000000000000090000 -4.99999999999999910000 0.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 2 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000008 0 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +TrimBy 00000009 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +TrimBy 00000007 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +TrimBy 00000006 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +AddSurface +Surface 00000006 00646464 00000000 2 1 +SCtrl 0 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 4.99999999999999910000 -5.00000000000000090000 -10.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 1 1 4.99999999999999910000 -5.00000000000000090000 0.00000000000000000000 Weight 0.70710678118654757000 +SCtrl 2 0 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000b 0 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 +TrimBy 0000000c 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 +TrimBy 0000000a 1 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +TrimBy 00000009 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +AddSurface +Curve 00000001 1 2 00000001 00000003 +CCtrl 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 +CurvePt 0 4.64894150531215190000 1.84047354780936390000 -10.00000000000000000000 +CurvePt 0 3.53553390593273780000 3.53553390593273780000 -10.00000000000000000000 +CurvePt 0 1.84047354780936410000 4.64894150531215190000 -10.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +AddCurve +Curve 00000002 1 2 00000002 00000003 +CCtrl 0 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 +CurvePt 0 4.64894150531215190000 1.84047354780936390000 0.00000000000000000000 +CurvePt 0 3.53553390593273780000 3.53553390593273780000 0.00000000000000000000 +CurvePt 0 1.84047354780936410000 4.64894150531215190000 0.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000003 1 1 00000003 00000004 +CCtrl 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000004 1 2 00000001 00000004 +CCtrl 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -4.99999999999999910000 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +CurvePt 0 -1.84047354780936320000 4.64894150531215190000 -10.00000000000000000000 +CurvePt 0 -3.53553390593273780000 3.53553390593273780000 -10.00000000000000000000 +CurvePt 0 -4.64894150531215190000 1.84047354780936430000 -10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +AddCurve +Curve 00000005 1 2 00000002 00000004 +CCtrl 0 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -4.99999999999999910000 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 -1.84047354780936320000 4.64894150531215190000 0.00000000000000000000 +CurvePt 0 -3.53553390593273780000 3.53553390593273780000 0.00000000000000000000 +CurvePt 0 -4.64894150531215190000 1.84047354780936430000 0.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +AddCurve +Curve 00000006 1 1 00000004 00000005 +CCtrl 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +AddCurve +Curve 00000007 1 2 00000001 00000005 +CCtrl 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000090000 -4.99999999999999910000 -10.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +CurvePt 0 -4.64894150531215190000 -1.84047354780936300000 -10.00000000000000000000 +CurvePt 0 -3.53553390593273860000 -3.53553390593273730000 -10.00000000000000000000 +CurvePt 0 -1.84047354780936480000 -4.64894150531215190000 -10.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +AddCurve +Curve 00000008 1 2 00000002 00000005 +CCtrl 0 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000090000 -4.99999999999999910000 0.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +CurvePt 0 -4.64894150531215190000 -1.84047354780936300000 0.00000000000000000000 +CurvePt 0 -3.53553390593273860000 -3.53553390593273730000 0.00000000000000000000 +CurvePt 0 -1.84047354780936480000 -4.64894150531215190000 0.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000009 1 1 00000005 00000006 +CCtrl 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 0000000a 1 2 00000001 00000006 +CCtrl 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 4.99999999999999910000 -5.00000000000000090000 -10.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +CurvePt 0 1.84047354780936300000 -4.64894150531215190000 -10.00000000000000000000 +CurvePt 0 3.53553390593273730000 -3.53553390593273860000 -10.00000000000000000000 +CurvePt 0 4.64894150531215190000 -1.84047354780936500000 -10.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 +AddCurve +Curve 0000000b 1 2 00000002 00000006 +CCtrl 0 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 4.99999999999999910000 -5.00000000000000090000 0.00000000000000000000 Weight 0.70710678118654757000 +CCtrl 2 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +CurvePt 0 1.84047354780936300000 -4.64894150531215190000 0.00000000000000000000 +CurvePt 0 3.53553390593273730000 -3.53553390593273860000 0.00000000000000000000 +CurvePt 0 4.64894150531215190000 -1.84047354780936500000 0.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 +AddCurve +Curve 0000000c 1 1 00000006 00000003 +CCtrl 0 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 +AddCurve diff --git a/test/constraint/pt_on_face/normal_v22.slvs b/test/constraint/pt_on_face/normal_v22.slvs new file mode 100644 index 0000000..03ee428 --- /dev/null +++ b/test/constraint/pt_on_face/normal_v22.slvs @@ -0,0 +1,687 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000003 +Group.type=5100 +Group.order=2 +Group.name=extrude +Group.activeWorkplane.v=00010000 +Group.opA.v=00000002 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.predef.entityB.v=80020000 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00040000 1002 + 2 00040001 1002 + 3 00040020 1002 + 4 00040040 1002 + 5 00040000 1001 + 6 00040001 1001 + 7 00040020 1001 + 8 00040040 1001 + 9 00040001 1003 + 10 80020000 1002 + 11 80020000 1001 + 12 80020001 1002 + 13 80020002 1002 + 14 80020001 1001 + 15 80020002 1001 + 16 80020002 1003 + 17 00000000 1001 + 18 00000000 1002 +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=80030000 +AddParam + +Param.h.v.=80030001 +AddParam + +Param.h.v.=80030002 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000003 +Request.construction=1 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030001 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.normal.v=80030003 +Entity.distance.v=80030004 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030004 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.normal.v=80030007 +Entity.distance.v=80030008 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.z=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030007 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=4001 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.point[1].v=80030002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.z=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.point[1].v=8003000d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.actPoint.z=-10.00000000000000000000 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003000d +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=43 +Constraint.group.v=00000003 +Constraint.ptA.v=00050000 +Constraint.entityA.v=80030012 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Surface 00000001 00646464 80030011 1 1 +SCtrl 0 0 -5.00000000000000000000 -5.00000000000000088818 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000010000000827 -5.00000000000000088818 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000000000 4.99999999999999911182 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 5.00000000010000000827 4.99999999999999911182 -10.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000a 0 -0.00000000000000177636 -5.00000000000000088818 -10.00000000000000000000 5.00000000000000000000 -0.00000000000000088818 -10.00000000000000000000 +TrimBy 00000001 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 0.00000000000000000000 4.99999999999999911182 -10.00000000000000000000 +TrimBy 00000004 0 0.00000000000000000000 4.99999999999999911182 -10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 +TrimBy 00000007 0 -5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 -0.00000000000000177636 -5.00000000000000088818 -10.00000000000000000000 +AddSurface +Surface 00000002 00646464 80030012 1 1 +SCtrl 0 0 5.00000000010000000827 -5.00000000000000088818 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 -5.00000000000000088818 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000010000000827 4.99999999999999911182 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 -5.00000000000000000000 4.99999999999999911182 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 1 -5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000000000 4.99999999999999911182 0.00000000000000000000 +TrimBy 00000002 1 0.00000000000000000000 4.99999999999999911182 0.00000000000000000000 5.00000000000000088818 -0.00000000000000000000 0.00000000000000000000 +TrimBy 0000000b 1 5.00000000000000088818 -0.00000000000000088818 0.00000000000000000000 -0.00000000000000088818 -5.00000000000000088818 0.00000000000000000000 +TrimBy 00000008 1 -0.00000000000000088818 -5.00000000000000088818 0.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000003 00646464 00000000 2 1 +SCtrl 0 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000000000000000 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000002 0 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000001 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 +TrimBy 0000000c 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 +TrimBy 00000003 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +AddSurface +Surface 00000004 00646464 00000000 2 1 +SCtrl 0 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -4.99999999999999911182 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 -4.99999999999999911182 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000005 0 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +TrimBy 00000004 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +TrimBy 00000003 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000006 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +AddSurface +Surface 00000005 00646464 00000000 2 1 +SCtrl 0 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000088818 -4.99999999999999911182 -10.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 -5.00000000000000088818 -4.99999999999999911182 0.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000008 0 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +TrimBy 00000007 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +TrimBy 00000006 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +TrimBy 00000009 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +AddSurface +Surface 00000006 00646464 00000000 2 1 +SCtrl 0 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 4.99999999999999911182 -5.00000000000000088818 -10.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 1 1 4.99999999999999911182 -5.00000000000000088818 0.00000000000000000000 Weight 0.70710678118654757274 +SCtrl 2 0 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 2 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000b 0 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 +TrimBy 0000000a 1 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +TrimBy 00000009 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000c 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 +AddSurface +Curve 00000001 1 2 00000001 00000003 +CCtrl 0 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 -10.00000000000000000000 +CurvePt 0 4.64894150531215188948 1.84047354780936389673 -10.00000000000000000000 +CurvePt 0 4.18497755609325672310 2.73604876692571252761 -10.00000000000000000000 +CurvePt 0 3.53553390593273775266 3.53553390593273775266 -10.00000000000000000000 +CurvePt 0 2.73604876692571252761 4.18497755609325672310 -10.00000000000000000000 +CurvePt 0 1.84047354780936411878 4.64894150531215188948 -10.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +AddCurve +Curve 00000002 1 2 00000002 00000003 +CCtrl 0 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 0.00000000000000000000 +CurvePt 0 4.64894150531215188948 1.84047354780936389673 0.00000000000000000000 +CurvePt 0 4.18497755609325672310 2.73604876692571252761 0.00000000000000000000 +CurvePt 0 3.53553390593273775266 3.53553390593273775266 0.00000000000000000000 +CurvePt 0 2.73604876692571252761 4.18497755609325672310 0.00000000000000000000 +CurvePt 0 1.84047354780936411878 4.64894150531215188948 0.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000003 1 1 00000003 00000004 +CCtrl 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000004 1 2 00000001 00000004 +CCtrl 0 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -4.99999999999999911182 5.00000000000000000000 -10.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 -10.00000000000000000000 +CurvePt 0 -1.84047354780936323060 4.64894150531215188948 -10.00000000000000000000 +CurvePt 0 -2.73604876692571208352 4.18497755609325672310 -10.00000000000000000000 +CurvePt 0 -3.53553390593273775266 3.53553390593273775266 -10.00000000000000000000 +CurvePt 0 -4.18497755609325672310 2.73604876692571297170 -10.00000000000000000000 +CurvePt 0 -4.64894150531215188948 1.84047354780936434082 -10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +AddCurve +Curve 00000005 1 2 00000002 00000004 +CCtrl 0 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -4.99999999999999911182 5.00000000000000000000 0.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 0.00000000000000030616 5.00000000000000000000 0.00000000000000000000 +CurvePt 0 -1.84047354780936323060 4.64894150531215188948 0.00000000000000000000 +CurvePt 0 -2.73604876692571208352 4.18497755609325672310 0.00000000000000000000 +CurvePt 0 -3.53553390593273775266 3.53553390593273775266 0.00000000000000000000 +CurvePt 0 -4.18497755609325672310 2.73604876692571297170 0.00000000000000000000 +CurvePt 0 -4.64894150531215188948 1.84047354780936434082 0.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +AddCurve +Curve 00000006 1 1 00000004 00000005 +CCtrl 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +AddCurve +Curve 00000007 1 2 00000001 00000005 +CCtrl 0 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000088818 -4.99999999999999911182 -10.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 -10.00000000000000000000 +CurvePt 0 -4.64894150531215188948 -1.84047354780936300855 -10.00000000000000000000 +CurvePt 0 -4.18497755609325672310 -2.73604876692571208352 -10.00000000000000000000 +CurvePt 0 -3.53553390593273864084 -3.53553390593273730858 -10.00000000000000000000 +CurvePt 0 -2.73604876692571297170 -4.18497755609325672310 -10.00000000000000000000 +CurvePt 0 -1.84047354780936478491 -4.64894150531215188948 -10.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +AddCurve +Curve 00000008 1 2 00000002 00000005 +CCtrl 0 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000088818 -4.99999999999999911182 0.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 0.00000000000000061232 0.00000000000000000000 +CurvePt 0 -4.64894150531215188948 -1.84047354780936300855 0.00000000000000000000 +CurvePt 0 -4.18497755609325672310 -2.73604876692571208352 0.00000000000000000000 +CurvePt 0 -3.53553390593273864084 -3.53553390593273730858 0.00000000000000000000 +CurvePt 0 -2.73604876692571297170 -4.18497755609325672310 0.00000000000000000000 +CurvePt 0 -1.84047354780936478491 -4.64894150531215188948 0.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000009 1 1 00000005 00000006 +CCtrl 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 0000000a 1 2 00000001 00000006 +CCtrl 0 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 4.99999999999999911182 -5.00000000000000088818 -10.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 -10.00000000000000000000 +CurvePt 0 1.84047354780936300855 -4.64894150531215188948 -10.00000000000000000000 +CurvePt 0 2.73604876692571163943 -4.18497755609325672310 -10.00000000000000000000 +CurvePt 0 3.53553390593273730858 -3.53553390593273864084 -10.00000000000000000000 +CurvePt 0 4.18497755609325583492 -2.73604876692571297170 -10.00000000000000000000 +CurvePt 0 4.64894150531215188948 -1.84047354780936500696 -10.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 +AddCurve +Curve 0000000b 1 2 00000002 00000006 +CCtrl 0 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 4.99999999999999911182 -5.00000000000000088818 0.00000000000000000000 Weight 0.70710678118654757274 +CCtrl 2 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -0.00000000000000091849 -5.00000000000000000000 0.00000000000000000000 +CurvePt 0 1.84047354780936300855 -4.64894150531215188948 0.00000000000000000000 +CurvePt 0 2.73604876692571163943 -4.18497755609325672310 0.00000000000000000000 +CurvePt 0 3.53553390593273730858 -3.53553390593273864084 0.00000000000000000000 +CurvePt 0 4.18497755609325583492 -2.73604876692571297170 0.00000000000000000000 +CurvePt 0 4.64894150531215188948 -1.84047354780936500696 0.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 +AddCurve +Curve 0000000c 1 1 00000006 00000003 +CCtrl 0 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 -10.00000000000000000000 +CurvePt 1 5.00000000000000000000 -0.00000000000000122465 0.00000000000000000000 +AddCurve diff --git a/test/constraint/pt_on_face/test.cpp b/test/constraint/pt_on_face/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/constraint/pt_on_face/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/constraint/pt_on_line/free_in_3d_v20.slvs b/test/constraint/pt_on_line/free_in_3d_v20.slvs new file mode 100644 index 0000000..bc5ecec --- /dev/null +++ b/test/constraint/pt_on_line/free_in_3d_v20.slvs @@ -0,0 +1,315 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-20.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-20.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=42 +Constraint.group.v=00000002 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/pt_on_line/free_in_3d_v22.slvs b/test/constraint/pt_on_line/free_in_3d_v22.slvs new file mode 100644 index 0000000..250f93c --- /dev/null +++ b/test/constraint/pt_on_line/free_in_3d_v22.slvs @@ -0,0 +1,315 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-20.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040015 +AddParam + +Param.h.v.=00050010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-20.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=42 +Constraint.group.v=00000002 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/pt_on_line/left_free_in_3d.png b/test/constraint/pt_on_line/left_free_in_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..13b2a6af4d1d36cefbff76aea8430f6bc7e0248e GIT binary patch literal 4319 zcmeHLYgAKL8a-SH50Q$cB1$AaD(#FYL8WF8MJY-d9y3ybKp@0|qC^O4QStzSMX3XV z)QUV*I;Da}La+$25yJHuDF`Yxse}Y53c{^~f-xkJ&#Xzb z{e64ylSBXX^R$>YcNze|!s{ase*hrj*kx*rz7cNAZ3V!x*vn)6=7bZEWm{r9#j_4} zT%~-F9a|K9mz}<_`kTmkvJLlfnVSwrkiO8~NmKRG!NS}OCVg=ffi`37jCQ5zDPjQ=s<=!Ocssgh zJ#@=YbDsA30?=^H=yjmuGvgo24Pf6F@FV69MM;3-Z$CjpZcy{Q6;os>BSI?J2QrF# z1Pk=)CUK$_aw7oyyn$n^!{5O1raMwF|IcB;#YB;VaIG#WTGen{$zFBSZrBgByK12H z9j#VAg=Wiv&0$h1fUEP92sez7>AI12m2Rjij3sl(im(>qK+Jz8la%^i)hLtaGEBWP z8c?ZdDhSmvhOhV4x@M;Hx`~QJw}Vf^-RRRZE{|zSgAsBqr#7d#)`mWs$lI!B`VTaw za~yFZ$MB}}YZ=+ALE7@mXv?p-?DE;sf1Hi|puRKrfWl;*t(60ykbQCWQTwkSR}a~O zp=0n*-mJJ4VB{#eQLx4P+>Nr?Z}P(B0fF4Tgq{i-Y@#Ih+2y}eR(l6=r6 ze~oMaoAmT=myRj9qwgcDLTwp_)UVPP+o%PyV+i>BOsz`|f=zvz%h^fUB#x4=?CdIR&HrZMsQJsVs{rF$RP$ z%${}IjV%Kft><30%~y(l5tm-ysv!5!#F~>QDGfvollobJxOdS>99uWz>I2)0LCx_W zJ`W`N%)J`T23x9fny2Zf9!|@pRR#cWHL>R8(N2jG3#v}e1??YAOqESq-KscVF>-LK zF|eyLW@*VRJ=&HARPLiefMWm0Cv{9)@NXTZwUd1%=)6w8`RC!afhrC0Tk=Q!>^HGe zJe!EL?2QKOMS0JVr(o^d;F_1C+$3z|N~$VP1nSXNwfUaB#tg9Ya`j%nMhQzD%f*$C ze*nO9T&t~!Fu_Nd!gLN>^!9M_!@ozqJM90$;|W zU+_kj0rsJZGAGaeQ`pk0(@`#m2Np>HoQkTQ)WaI!PwGgi3(W!HdlaB5w{lL4ohyK6 zHjW)btj5nGo6A@B6z*$Z=PNmU9{oaaGo)9N<@HW5z@PeM{5MAv6=}8<*od$x z0ZMG}QKwrF+XL}6?WgIsZm$$sl$yDX)tH7p{WTi{ zus@-tjB&r~Sqp4}kOMa%;BF*7UCeJb0bRSxV8)vD4BFRE?tIfDc8=9*ixxMH-mmm$ zK4LAqRGS6jIzm(8X&Ym|z$0wuq!I1vAKGC5^^#vCG!jzA-Uk$DLZ)b%p}C;9=* znCHv)DI$9wo6kN!5}_ebCbcL{+!z=MMTl0#;>*61j^uv$3nlONP`P>8VoOa+I%2bSmCpKn^n89cg^z)d=%I^CaO?CQ$TJbpFV{H?TzBN0MUYM4uL6oCJtlrOBd zem8s$B71waXKA5}BIuJ_8gU_Ado4bqt6PMgmv!A@3sNurOH8+Oy)+*5fQX97QU!uKqB9}|l*&NZT5tzJktN`8D=?rD0wa+%5L~bp1s4Pn zHK^5sD z02m%cus}msx&OCVEnvC{!hi#s=746Iss`qmF#))vst=q`|Bn{+tZmNa|HKL%7V$Cz zlV!{Kr5q7VrV0YuC8)xTbl zB2r^YU$3A~h-^(jJAV3uJ(pP3HQIwF*YO3+3dH)AI>3dFRZff6dulaJ?l?PTUrtbW zCVyYNU<%Qgg{(3kG8RC88VC7364H_%>=DfZU+IjqlwrXojT0^MRCOf>D| z1=VMU7PevP^WJ9%3^(Q}1#x&BL~5k4SNw z6}fwxG?AYg)%2O^Vf$X5GF9oYD%Zd$RV0f#qNyJ!71Ro4Zz66ERd|_jmnAuMcyF3; zj(`Kq28ocxx>RX45(L8nso9T}PAMhQFEhKI32wgO(;MyM?!!WLOEcY3mFt=}g-a6c zM$(V-+Ie5OD>iM2yj3818$I$?n%fQ@>-t&F!MDzgni;8s1qa-FhsftN`ilAGJAuc~s?*60N~%p==#MzibaGr? zf5gK;7cu+9qZ$9jzi95sWoX;g)tJ81;~T%H+sSG63QIa)LSol)bPF=@e4!Ly+rp8DWmos*mX*-Qj0c@vQ0 zL`{mX@eIEmuVGFjkFK;WmEL}ae|@V8=(#`EP?_a0?)M zMOut_3ws-YoF5~8{p^$E4u3bE_CykX!sU;U{5Np#@ zoxTH5L}i)*h%)MGT|~Ihwpa(OvFTP7)b0*T7}7gLG4^(F!~xs=E-EBKPV^E6=x9>m%8E-wVsSP>QAD~t=s6tdlY+-|+$q75iUP{}9i_42NZG^ELcM`1 zx^KQN6^A;|x6_=`&2=*gDZ?0N63j;`GOnz6R3Iprt6FcM;A+JAG*G$>@q%Po4|X9n@p$=FSWN+)RA~n4y9)B6eVbTn4o_!JR*7)auU63 z)Ssl_mCIKLa9-!yh!Y5zoEL4u&2K5 zbtcL#CX;nCUbOx2I>9<5;Vqk>0E9vdW^Etwdzk#Ugfu=Dc>EybLEAm_GbGtsImaoa zl$^}Y>{l8$De`NM{6tt9F1tzrwg-b%QFxu*u2l{KyZqM&QJ5PjBlJ+@4tQQRUcc$8 zWLQ0axr~Dol=6N~5@&Tb_YZHJ4?HfkBNhL)Uo7x?%)Xuz)^_!H`#`r@D zeWO`xmvZCEHHy49)dAW!SJ`eu%hqwM(U!UPi43}((09i}35t)GRZ)VU^SKhVTuu_C zx%X1D)VMbPYN3Fr`J+kvfHWlwKmKa;sTb?XQ9b>g6lxCzYaIA7dfzGky#VXoJY0*v HhW7mm%p2UJ literal 0 HcmV?d00001 diff --git a/test/constraint/pt_on_line/normal.slvs b/test/constraint/pt_on_line/normal.slvs new file mode 100644 index 0000000..6bd8985 --- /dev/null +++ b/test/constraint/pt_on_line/normal.slvs @@ -0,0 +1,318 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=40000001 +Param.val=0.33333333333333331483 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=42 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valP.v=40000001 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/pt_on_line/normal_v20.slvs b/test/constraint/pt_on_line/normal_v20.slvs new file mode 100644 index 0000000..2c3eec5 --- /dev/null +++ b/test/constraint/pt_on_line/normal_v20.slvs @@ -0,0 +1,315 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=42 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/pt_on_line/normal_v22.slvs b/test/constraint/pt_on_line/normal_v22.slvs new file mode 100644 index 0000000..5a74c34 --- /dev/null +++ b/test/constraint/pt_on_line/normal_v22.slvs @@ -0,0 +1,313 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=42 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/pt_on_line/right_free_in_3d.png b/test/constraint/pt_on_line/right_free_in_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..fca580fa96ba0cdef71493b8f3e7fdee37dd32ed GIT binary patch literal 4316 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 X-ZE6bB!|P_QNU)$Z4L$NiKqVuaAqb_ literal 0 HcmV?d00001 diff --git a/test/constraint/pt_pt_distance/free_in_3d.slvs b/test/constraint/pt_pt_distance/free_in_3d.slvs new file mode 100644 index 0000000..3da9edc --- /dev/null +++ b/test/constraint/pt_pt_distance/free_in_3d.slvs @@ -0,0 +1,289 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=30 +Constraint.group.v=00000002 +Constraint.valA=10.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.y=-1.49454882168925862196 +AddConstraint + diff --git a/test/constraint/pt_pt_distance/free_in_3d_v20.slvs b/test/constraint/pt_pt_distance/free_in_3d_v20.slvs new file mode 100644 index 0000000..e9b6d25 --- /dev/null +++ b/test/constraint/pt_pt_distance/free_in_3d_v20.slvs @@ -0,0 +1,289 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=30 +Constraint.group.v=00000002 +Constraint.valA=10.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.y=-1.49454882168925860000 +AddConstraint + diff --git a/test/constraint/pt_pt_distance/free_in_3d_v22.slvs b/test/constraint/pt_pt_distance/free_in_3d_v22.slvs new file mode 100644 index 0000000..c5b1af5 --- /dev/null +++ b/test/constraint/pt_pt_distance/free_in_3d_v22.slvs @@ -0,0 +1,289 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=30 +Constraint.group.v=00000002 +Constraint.valA=10.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +Constraint.disp.offset.y=-1.49454882168925862196 +AddConstraint + diff --git a/test/constraint/pt_pt_distance/normal.png b/test/constraint/pt_pt_distance/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..b6524acec3e455fd5269d5b37649293ffc878ab9 GIT binary patch literal 4467 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 literal 0 HcmV?d00001 diff --git a/test/constraint/pt_pt_distance/reference.slvs b/test/constraint/pt_pt_distance/reference.slvs new file mode 100644 index 0000000..2cf72b5 --- /dev/null +++ b/test/constraint/pt_pt_distance/reference.slvs @@ -0,0 +1,289 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=30 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=10.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.y=2.50000000000000000000 +AddConstraint + diff --git a/test/constraint/pt_pt_distance/reference_v20.slvs b/test/constraint/pt_pt_distance/reference_v20.slvs new file mode 100644 index 0000000..0888aa8 --- /dev/null +++ b/test/constraint/pt_pt_distance/reference_v20.slvs @@ -0,0 +1,289 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=30 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=10.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.y=2.50000000000000000000 +AddConstraint + diff --git a/test/constraint/pt_pt_distance/reference_v22.slvs b/test/constraint/pt_pt_distance/reference_v22.slvs new file mode 100644 index 0000000..8a3b2a3 --- /dev/null +++ b/test/constraint/pt_pt_distance/reference_v22.slvs @@ -0,0 +1,289 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=30 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.valA=10.00000000000000000000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=1 +Constraint.disp.offset.y=2.50000000000000000000 +AddConstraint + diff --git a/test/constraint/pt_pt_distance/test.cpp b/test/constraint/pt_pt_distance/test.cpp new file mode 100644 index 0000000..97bef40 --- /dev/null +++ b/test/constraint/pt_pt_distance/test.cpp @@ -0,0 +1,49 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(free_in_3d_roundtrip) { + CHECK_LOAD("free_in_3d.slvs"); + CHECK_RENDER("free_in_3d.png"); + CHECK_SAVE("free_in_3d.slvs"); +} + +TEST_CASE(free_in_3d_migrate_from_v20) { + CHECK_LOAD("free_in_3d_v20.slvs"); + CHECK_SAVE("free_in_3d.slvs"); +} + +TEST_CASE(free_in_3d_migrate_from_v22) { + CHECK_LOAD("free_in_3d_v22.slvs"); + CHECK_SAVE("free_in_3d.slvs"); +} + +TEST_CASE(reference_roundtrip) { + CHECK_LOAD("reference.slvs"); + CHECK_RENDER("reference.png"); + CHECK_SAVE("reference.slvs"); +} + +TEST_CASE(reference_migrate_from_v20) { + CHECK_LOAD("reference_v20.slvs"); + CHECK_SAVE("reference.slvs"); +} + +TEST_CASE(reference_migrate_from_v22) { + CHECK_LOAD("reference_v22.slvs"); + CHECK_SAVE("reference.slvs"); +} diff --git a/test/constraint/same_orientation/normal.png b/test/constraint/same_orientation/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..7bcd4c59ea0425d118cf4ed2a1cd73861fdbc00a GIT binary patch literal 4472 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 b^jo<$rr0iEKi7l*Q32cBw!7wTCM5hDA_6I) literal 0 HcmV?d00001 diff --git a/test/constraint/same_orientation/normal.slvs b/test/constraint/same_orientation/normal.slvs new file mode 100644 index 0000000..fcadecd --- /dev/null +++ b/test/constraint/same_orientation/normal.slvs @@ -0,0 +1,309 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040020 +Param.val=0.70710678118654757274 +AddParam + +Param.h.v.=00040021 +Param.val=-0.70710678118654746172 +AddParam + +Param.h.v.=00040022 +AddParam + +Param.h.v.=00040023 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=40000002 +Param.val=1.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.actNormal.w=0.70710678118654757274 +Entity.actNormal.vx=-0.70710678118654746172 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000002 +Constraint.type=110 +Constraint.group.v=00000002 +Constraint.valP.v=40000002 +Constraint.entityA.v=00040020 +Constraint.entityB.v=00030020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/same_orientation/normal_v20.slvs b/test/constraint/same_orientation/normal_v20.slvs new file mode 100644 index 0000000..99cbe1f --- /dev/null +++ b/test/constraint/same_orientation/normal_v20.slvs @@ -0,0 +1,302 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040020 +Param.val=0.70710678118654757000 +AddParam + +Param.h.v.=00040021 +Param.val=-0.70710678118654746000 +AddParam + +Param.h.v.=00040022 +AddParam + +Param.h.v.=00040023 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.actNormal.w=0.70710678118654757000 +Entity.actNormal.vx=-0.70710678118654746000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000002 +Constraint.type=110 +Constraint.group.v=00000002 +Constraint.entityA.v=00040020 +Constraint.entityB.v=00030020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/same_orientation/normal_v22.slvs b/test/constraint/same_orientation/normal_v22.slvs new file mode 100644 index 0000000..c29b8d7 --- /dev/null +++ b/test/constraint/same_orientation/normal_v22.slvs @@ -0,0 +1,304 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040020 +Param.val=0.70710678118654757000 +AddParam + +Param.h.v.=00040021 +Param.val=-0.70710678118654746000 +AddParam + +Param.h.v.=00040022 +AddParam + +Param.h.v.=00040023 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.actNormal.w=0.70710678118654757000 +Entity.actNormal.vx=-0.70710678118654746000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000002 +Constraint.type=110 +Constraint.group.v=00000002 +Constraint.entityA.v=00040020 +Constraint.entityB.v=00030020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/same_orientation/same_group.png b/test/constraint/same_orientation/same_group.png new file mode 100644 index 0000000000000000000000000000000000000000..ce3f77ba7366743776aa46ad410fb2377f579c11 GIT binary patch literal 4376 zcmeHL3p7;g9{)ThgG8KAbd*+f4>BT;lQX@P9Q1M~6Jd%t#duUi2E!4FLpSA>j_z`! zGbS_9%s3dfZYPRk@@UB9(Lqz5$zd=v=I#+KQ%UQrd(T>Tt-IFD>^@fW!S9;xuocm{wbk7@k7}hzLkvv=0F%mKV*qgG z(u!b>15OIW${PYADjES)^r!&blT`=XG9YPyRD*AQ^uH8D)xq1kw~+As{v1jef6T+` zCn2YD?j!nPz=%^iuHE8Y{9~C_K&v0!m610ZXCJ%hd>%CX;T;V@S@l{H^QPW(N1Mt4 zqKeu=D}^lCv~UE7N6rPXHL$yd0Q#rHY)Vj+JOgiui!Pio&1!lu1!y`ph3%9`Fa5hG z9ZV!3W@bnX&(HdUJB95Az|v+efLZ}<>XQR{$79}>hZwfm&Ezu+16k5a?zoFbri<;C_nYd6b_qsW& zFvC$?b)QY+>+jjqX?;7WLn1--&NQlEaM(kX!Z2c}RB*LK+$K@+3oi0TZFV%xUn9q} z$ht2*xf6eSV)p=DL=IamzfCJ0DfL#=T3geRqVlCB!{Hc=QKIv=RZ-%5&#$^>JOd@= z|Ky`U{k^Pw>FSN66Cl)jjszd*DQ=+_M_i+5vpUzjs9Hb*Pi7`bY&%aegrYVvd0G_>~oief*0iZ zeAnq$BnRWSm;vaUKSOeWqTqe4$nS_M7O+`@#|y|LkM=}RQomrT<{Mu`G_4RHzXX)% zs~Hk6<+Lk;y|9|c3P|ZSsxE=LGrF_wF=fi*dnuMZPU>0o33Y3WXhb$gCxD(ZO*mFL zEy%Bl;jW_+g9PRs1u32Go4#;)Z7r(~$z*s}ojy!D8DRD1Y|fKP{G`x>lP~tZ&M46^ zkLi!$-02pU_nMpWgL}||zKD=3LXN0-pz`^%$68pK>r12c0&;w5w~edD`grk_7q0I4 z&Xs84S8GT=TSQt5E`WV6edG;7^`sRwtH17O<}@p0ZFVzK!$Q9>nM*BOofI8zcZJCt z5>ZYD5XTJ2CK%ixjbdU!YLJ~u=z1|*kLb#NLLlIPDQRCf&N3wsxNo+kn(+@U0t?<( z)=xASFKBq9_!V=qJ`=dO9effdIBmU7==S?wx3Ecrj21dg`_P=x&IEpjLC;GJY({L_ z;9~%IUT@u*7?eSfVVwLr;9VE7i00zlIqXx|_mw}Krwf4hv02CeWJ3nG2QDr2oigBA zs{Grw?ZMGEO3z5I%Gb`x?jo%mF%0iodlVtj^UWwd02v9h*POTJkCgQPU=WMU{{|RZ zM-l7}Cfxjour#n3T}4n*z+DQryjxTys0g=UAB84soPLeM2sz<~04S3MU2uMvK89eI z(pRqnT|dn>3aDkN0bPgQ?{_3YO17Yu_pAaL`vY+JYcdR0er^BBGUBM zqFDH^XEH=Omq31);VlJaz?3LTX!<4--p59sG44X6?JxEAIVa&Lm2ny%@XOI7x`Z9M zwz{D1WTQM=zxXK* zs$%uM#9KA2z9+oxc70SJ+}AsHZEYdI9bxg&Sp5k7NuExMOnFdawGkA~(!#1mZZiXc z%iz74nkRCXk-z#o0^FCM+1SXFona^-mtzAqGq0loD*ZHq&6jVynb7EynwQJBoZ|7) ztf!rMS6y~;Bc2K)WQi8_E3me1rP;iOO8!7&XK9v8)gEj2=u{o13Wd#SkGDCYN zd*uLT^C=uNC2w8RE#86kNSxtxHL?WL6xOY+GcG`1K~I(0o1FP$QzvIlc+br$icPz1 ze;GZ8!5D07Il?c??I#QiOO4%%gpVRCOLKC099l%;Y2bf1$J@1qB_h14w0aZF7#MYD zaNAl4A+E`UcAx69vEhnSTUSp`uOH_>$+?0C-R`>Czxkw= z@>16}rBvN9+`$q(qmk{$&{y4WcH8rK`!5)WejfM}hf;TRge%z&qP~P=^v@?XUA+$A9S6V@hsMYj~IW|%K@R>M&D!G*W*@Qf!}xlyIp&23U}aQ{tfia`LzH5 literal 0 HcmV?d00001 diff --git a/test/constraint/same_orientation/same_group.slvs b/test/constraint/same_orientation/same_group.slvs new file mode 100644 index 0000000..7944b1e --- /dev/null +++ b/test/constraint/same_orientation/same_group.slvs @@ -0,0 +1,379 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-25.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040020 +Param.val=0.70710678118654757274 +AddParam + +Param.h.v.=00040021 +AddParam + +Param.h.v.=00040022 +Param.val=0.70710678118654746172 +AddParam + +Param.h.v.=00040023 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00050020 +Param.val=0.70710678118654757274 +AddParam + +Param.h.v.=00050021 +AddParam + +Param.h.v.=00050022 +Param.val=-0.70710678118654757274 +AddParam + +Param.h.v.=00050023 +AddParam + +Param.h.v.=00050040 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=40000002 +Param.val=-1.00000000000000022204 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=400 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-25.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.actNormal.w=0.70710678118654757274 +Entity.actNormal.vy=0.70710678118654746172 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.normal.v=00050020 +Entity.distance.v=00050040 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.actNormal.w=0.70710678118654757274 +Entity.actNormal.vy=-0.70710678118654757274 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050040 +Entity.type=4000 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000002 +Constraint.type=110 +Constraint.group.v=00000002 +Constraint.valP.v=40000002 +Constraint.entityA.v=00050020 +Constraint.entityB.v=00040020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/same_orientation/test.cpp b/test/constraint/same_orientation/test.cpp new file mode 100644 index 0000000..9377b28 --- /dev/null +++ b/test/constraint/same_orientation/test.cpp @@ -0,0 +1,23 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(same_group_roundtrip) { + CHECK_LOAD("same_group.slvs"); + CHECK_RENDER("same_group.png"); + CHECK_SAVE("same_group.slvs"); +} diff --git a/test/constraint/symmetric/free_in_3d.png b/test/constraint/symmetric/free_in_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..58c2ecc2fff5fa2427a8f6192802cb3b8bd5dc32 GIT binary patch literal 4378 zcmeHLX;f2J9=}NltLzv+6s#T8QG@`pjDVpOM2o0^WkT3uWwC;yZNnCp1ausz5-S}9 zMZ{7?g#a?fU^MV33aDifAwiS`ijru6AP|tyKpzNbTuzyDW+FZbmB@BjX` z_tJK3b5~bgq6z>|-?G`&8vrDJ`lGA}->D4Cehh$C_ZHXBcE;xRcfL6IsNS%)`}VQZ z2Tb<&umx~f~BH?*2*#feo?>x=j{K{q&|;_IfCnq(D7j| zEg<=K_r~x2wB~jPyVbNN|K(FnU)ZL zpY*xhZfqHRoV605Xz6}vf6lnx>+wq9wDQ~pUw!;08G!P)&Nc#D@{>c@0{bMaE8i&w zLeF>0;BcOM0&w(}!iO5(Op7bwA)uA>E487syl?=!dam-LMZ%1n1)hoYidmZji1+!f zQ%mlYJ?RWayeCCSV~qCw&5 z1=`6Y$|qtUou>ex&2vqD7j3d^!SiJxa>X1R@qt7`2@0fI%~lRoqz_~-5ViRx_q^Gt zd^@&Q7(9Z%XjJVJ3(oK-M#}?1?g^FG z@_GO%;zvj@R+Bei;UMbD!85yVJ#?|ew(Ay^BGCZp;==UjiPW@rWxdz? zS^*%#lAt^u#71;s6)?{Av{8rhLztzTXUftVDUji=QigT#zyaHk@~ti&x+1%|fhUzv zWiR>3>1OsORP;zs>15@^OKH}*=9FPrFfE9u&;l#Cs1w%7qeaq_rd5*#HuR`Ok+^EI zzhk(iqr5+Xe>P72c2Z=VCQh2SFEmkfHdK}p@%cU?={rk>^nhe~o7I#?$@ftf{D#7S z3xCHAB=s6im8l{1L4~~UiRwXP@wP5tZ2)yAd zR!^uR>->|e^H!u{W>1xf2y8UPCx;MGauZu7syLP8dDsDAuc>Qh(BA*#?qAfpY+&dN zSTU^F$k=66bSj~$WGemgGj2#adJqYtWyCiCF}MM~7~bO&nvO2RQPHRlE4Gdwn9#;! zkJFioof{R}PeLhFN8N2J)QC6|)ut)5P!Y6e&hkaNYe?O(NXYtR&J$iol;F#Dpzn#G zEqQ99m&*^jsZYs#+v2KKwobkHR*9NP$U5;rbkh=iY`Q7tJ@zG)W4IZ28hI2(936oH?TRw%p$eoICBRZ z0Oifbyk^bJs4l;^Q`l=3Pcx?2wk+YE4Z!AiKm^PWXt1bo5_1l&b+jC&3th9Ab2rEl zK*kx`Fmq7TU%O_)iD}9Xam+~Ef8ix8-;XZ4HX}D77RnkSpWF8AtE%sY!95*?HqHqXJ zRFaXW{{=6mm#K2T#UxF8S|V#LliG*(t!7Ke;@i!fM+w}|M(_HknxwIqqhou{h`6J| z-NrH=Cy~eM93wL$apeQ<+=tRiCxU-2YVc}M5|7z+xC=%ScBJNI%v0vI`3?E}sly*ih@8^MNtC0910xucl z?bOGIza*s7H7%TK$Oy2iWsFM<+>XhR)*lwt$44b|OS_%gv`2a4xQRS5W1v7%#6fiF z!SEBiwRjag^J5}N7FT)XhBk3ckbpl-+BY6XrC;VJ16GTeOJqw(mZf=Cj^vHWTTswn zM~v{|xX=)%bQZ1eX-;A5aJK&;pl4sZ?w9b+8^=bUS1Frxay%Sy&&nIaIjl~Ps0UZw zOJ($?8nXD&zjeeoV53^vRv2%7nERl+n@tMPm@>Ctnz1e8#eQwyOWAn{aAYU>`$4bc z31P)d)96t=j>K!abxK0V4N22Pwe@0}gdQcM(T(88;sP`K5-(#e0{VK!ErL_!!{dh@ i58vkafA1~Agwoo9h=Lbv>tXnv2iW4a&9%gZnDPgg(dEJb literal 0 HcmV?d00001 diff --git a/test/constraint/symmetric/free_in_3d.slvs b/test/constraint/symmetric/free_in_3d.slvs new file mode 100644 index 0000000..863a794 --- /dev/null +++ b/test/constraint/symmetric/free_in_3d.slvs @@ -0,0 +1,289 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00060010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060012 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=60 +Constraint.group.v=00000002 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00060000 +Constraint.entityA.v=00030000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/symmetric/free_in_3d_v20.slvs b/test/constraint/symmetric/free_in_3d_v20.slvs new file mode 100644 index 0000000..a6a0cea --- /dev/null +++ b/test/constraint/symmetric/free_in_3d_v20.slvs @@ -0,0 +1,287 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00060010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060012 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=60 +Constraint.group.v=00000002 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00060000 +Constraint.entityA.v=00030000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/symmetric/free_in_3d_v22.slvs b/test/constraint/symmetric/free_in_3d_v22.slvs new file mode 100644 index 0000000..ff95075 --- /dev/null +++ b/test/constraint/symmetric/free_in_3d_v22.slvs @@ -0,0 +1,289 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050012 +AddParam + +Param.h.v.=00060010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060012 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=101 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=60 +Constraint.group.v=00000002 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00060000 +Constraint.entityA.v=00030000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/symmetric/normal.png b/test/constraint/symmetric/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..d497e0f9c92c90575f962909c72011949f8a1c06 GIT binary patch literal 4362 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 literal 0 HcmV?d00001 diff --git a/test/constraint/symmetric_line/normal.slvs b/test/constraint/symmetric_line/normal.slvs new file mode 100644 index 0000000..22a436a --- /dev/null +++ b/test/constraint/symmetric_line/normal.slvs @@ -0,0 +1,338 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=63 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00060000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/symmetric_line/normal_v20.slvs b/test/constraint/symmetric_line/normal_v20.slvs new file mode 100644 index 0000000..f1c4562 --- /dev/null +++ b/test/constraint/symmetric_line/normal_v20.slvs @@ -0,0 +1,336 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=63 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00060000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/symmetric_line/normal_v22.slvs b/test/constraint/symmetric_line/normal_v22.slvs new file mode 100644 index 0000000..f0d4dca --- /dev/null +++ b/test/constraint/symmetric_line/normal_v22.slvs @@ -0,0 +1,338 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-15.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=-10.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-15.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=63 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050000 +Constraint.ptB.v=00060000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/symmetric_line/test.cpp b/test/constraint/symmetric_line/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/constraint/symmetric_line/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/constraint/symmetric_vert/normal.png b/test/constraint/symmetric_vert/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..58c2ecc2fff5fa2427a8f6192802cb3b8bd5dc32 GIT binary patch literal 4378 zcmeHLX;f2J9=}NltLzv+6s#T8QG@`pjDVpOM2o0^WkT3uWwC;yZNnCp1ausz5-S}9 zMZ{7?g#a?fU^MV33aDifAwiS`ijru6AP|tyKpzNbTuzyDW+FZbmB@BjX` z_tJK3b5~bgq6z>|-?G`&8vrDJ`lGA}->D4Cehh$C_ZHXBcE;xRcfL6IsNS%)`}VQZ z2Tb<&umx~f~BH?*2*#feo?>x=j{K{q&|;_IfCnq(D7j| zEg<=K_r~x2wB~jPyVbNN|K(FnU)ZL zpY*xhZfqHRoV605Xz6}vf6lnx>+wq9wDQ~pUw!;08G!P)&Nc#D@{>c@0{bMaE8i&w zLeF>0;BcOM0&w(}!iO5(Op7bwA)uA>E487syl?=!dam-LMZ%1n1)hoYidmZji1+!f zQ%mlYJ?RWayeCCSV~qCw&5 z1=`6Y$|qtUou>ex&2vqD7j3d^!SiJxa>X1R@qt7`2@0fI%~lRoqz_~-5ViRx_q^Gt zd^@&Q7(9Z%XjJVJ3(oK-M#}?1?g^FG z@_GO%;zvj@R+Bei;UMbD!85yVJ#?|ew(Ay^BGCZp;==UjiPW@rWxdz? zS^*%#lAt^u#71;s6)?{Av{8rhLztzTXUftVDUji=QigT#zyaHk@~ti&x+1%|fhUzv zWiR>3>1OsORP;zs>15@^OKH}*=9FPrFfE9u&;l#Cs1w%7qeaq_rd5*#HuR`Ok+^EI zzhk(iqr5+Xe>P72c2Z=VCQh2SFEmkfHdK}p@%cU?={rk>^nhe~o7I#?$@ftf{D#7S z3xCHAB=s6im8l{1L4~~UiRwXP@wP5tZ2)yAd zR!^uR>->|e^H!u{W>1xf2y8UPCx;MGauZu7syLP8dDsDAuc>Qh(BA*#?qAfpY+&dN zSTU^F$k=66bSj~$WGemgGj2#adJqYtWyCiCF}MM~7~bO&nvO2RQPHRlE4Gdwn9#;! zkJFioof{R}PeLhFN8N2J)QC6|)ut)5P!Y6e&hkaNYe?O(NXYtR&J$iol;F#Dpzn#G zEqQ99m&*^jsZYs#+v2KKwobkHR*9NP$U5;rbkh=iY`Q7tJ@zG)W4IZ28hI2(936oH?TRw%p$eoICBRZ z0Oifbyk^bJs4l;^Q`l=3Pcx?2wk+YE4Z!AiKm^PWXt1bo5_1l&b+jC&3th9Ab2rEl zK*kx`Fmq7TU%O_)iD}9Xam+~Ef8ix8-;XZ4HX}D77RnkSpWF8AtE%sY!95*?HqHqXJ zRFaXW{{=6mm#K2T#UxF8S|V#LliG*(t!7Ke;@i!fM+w}|M(_HknxwIqqhou{h`6J| z-NrH=Cy~eM93wL$apeQ<+=tRiCxU-2YVc}M5|7z+xC=%ScBJNI%v0vI`3?E}sly*ih@8^MNtC0910xucl z?bOGIza*s7H7%TK$Oy2iWsFM<+>XhR)*lwt$44b|OS_%gv`2a4xQRS5W1v7%#6fiF z!SEBiwRjag^J5}N7FT)XhBk3ckbpl-+BY6XrC;VJ16GTeOJqw(mZf=Cj^vHWTTswn zM~v{|xX=)%bQZ1eX-;A5aJK&;pl4sZ?w9b+8^=bUS1Frxay%Sy&&nIaIjl~Ps0UZw zOJ($?8nXD&zjeeoV53^vRv2%7nERl+n@tMPm@>Ctnz1e8#eQwyOWAn{aAYU>`$4bc z31P)d)96t=j>K!abxK0V4N22Pwe@0}gdQcM(T(88;sP`K5-(#e0{VK!ErL_!!{dh@ i58vkafA1~Agwoo9h=Lbv>tXnv2iW4a&9%gZnDPgg(dEJb literal 0 HcmV?d00001 diff --git a/test/constraint/symmetric_vert/normal.slvs b/test/constraint/symmetric_vert/normal.slvs new file mode 100644 index 0000000..dc23f9e --- /dev/null +++ b/test/constraint/symmetric_vert/normal.slvs @@ -0,0 +1,287 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=62 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/symmetric_vert/normal_v20.slvs b/test/constraint/symmetric_vert/normal_v20.slvs new file mode 100644 index 0000000..c6f5a6d --- /dev/null +++ b/test/constraint/symmetric_vert/normal_v20.slvs @@ -0,0 +1,285 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=62 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/symmetric_vert/normal_v22.slvs b/test/constraint/symmetric_vert/normal_v22.slvs new file mode 100644 index 0000000..602bee1 --- /dev/null +++ b/test/constraint/symmetric_vert/normal_v22.slvs @@ -0,0 +1,287 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=62 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/symmetric_vert/test.cpp b/test/constraint/symmetric_vert/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/constraint/symmetric_vert/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/constraint/vertical/line.png b/test/constraint/vertical/line.png new file mode 100644 index 0000000000000000000000000000000000000000..096ca416be9b9c37379affb409e5e33f63df89b4 GIT binary patch literal 4331 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 literal 0 HcmV?d00001 diff --git a/test/constraint/vertical/pt_pt.slvs b/test/constraint/vertical/pt_pt.slvs new file mode 100644 index 0000000..0381c60 --- /dev/null +++ b/test/constraint/vertical/pt_pt.slvs @@ -0,0 +1,287 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/vertical/pt_pt_v20.slvs b/test/constraint/vertical/pt_pt_v20.slvs new file mode 100644 index 0000000..648dbca --- /dev/null +++ b/test/constraint/vertical/pt_pt_v20.slvs @@ -0,0 +1,285 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/vertical/pt_pt_v22.slvs b/test/constraint/vertical/pt_pt_v22.slvs new file mode 100644 index 0000000..458c241 --- /dev/null +++ b/test/constraint/vertical/pt_pt_v22.slvs @@ -0,0 +1,287 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040000 +Constraint.ptB.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/constraint/vertical/test.cpp b/test/constraint/vertical/test.cpp new file mode 100644 index 0000000..c08b5e1 --- /dev/null +++ b/test/constraint/vertical/test.cpp @@ -0,0 +1,33 @@ +#include "harness.h" + +TEST_CASE(line_roundtrip) { + CHECK_LOAD("line.slvs"); + CHECK_RENDER("line.png"); + CHECK_SAVE("line.slvs"); +} + +TEST_CASE(line_migrate_from_v20) { + CHECK_LOAD("line_v20.slvs"); + CHECK_SAVE("line.slvs"); +} + +TEST_CASE(line_migrate_from_v22) { + CHECK_LOAD("line_v22.slvs"); + CHECK_SAVE("line.slvs"); +} + +TEST_CASE(pt_pt_roundtrip) { + CHECK_LOAD("pt_pt.slvs"); + CHECK_RENDER("pt_pt.png"); + CHECK_SAVE("pt_pt.slvs"); +} + +TEST_CASE(pt_pt_migrate_from_v20) { + CHECK_LOAD("pt_pt_v20.slvs"); + CHECK_SAVE("pt_pt.slvs"); +} + +TEST_CASE(pt_pt_migrate_from_v22) { + CHECK_LOAD("pt_pt_v22.slvs"); + CHECK_SAVE("pt_pt.slvs"); +} diff --git a/test/constraint/where_dragged/free_in_3d.png b/test/constraint/where_dragged/free_in_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..7ee4884d2052ce8d658088ff381281a3e7c51422 GIT binary patch literal 4257 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, 3.1415926); +} + +TEST_CASE(literal) { + Expr *e; + CHECK_PARSE(e, "42"); + CHECK_TRUE(e->Eval() == 42); + CHECK_PARSE(e, "42.5"); + CHECK_TRUE(e->Eval() == 42.5); + CHECK_PARSE(e, "1_000_000"); + CHECK_TRUE(e->Eval() == 1000000); +} + +TEST_CASE(unary_ops) { + Expr *e; + CHECK_PARSE(e, "-10"); + CHECK_TRUE(e->Eval() == -10); +} + +TEST_CASE(binary_ops) { + Expr *e; + CHECK_PARSE(e, "1 + 2"); + CHECK_TRUE(e->Eval() == 3); + CHECK_PARSE(e, "1 - 2"); + CHECK_TRUE(e->Eval() == -1); + CHECK_PARSE(e, "3 * 4"); + CHECK_TRUE(e->Eval() == 12); + CHECK_PARSE(e, "3 / 4"); + CHECK_TRUE(e->Eval() == 0.75); +} + +TEST_CASE(parentheses) { + Expr *e; + CHECK_PARSE(e, "(1 + 2) * 3"); + CHECK_TRUE(e->Eval() == 9); + CHECK_PARSE(e, "1 + (2 * 3)"); + CHECK_TRUE(e->Eval() == 7); +} + +TEST_CASE(functions) { + Expr *e; + CHECK_PARSE(e, "sqrt(2)"); + CHECK_EQ_EPS(e->Eval(), 1.414213); + CHECK_PARSE(e, "square(3)"); + CHECK_EQ_EPS(e->Eval(), 9); + CHECK_PARSE(e, "sin(180)"); + CHECK_EQ_EPS(e->Eval(), 0); + CHECK_PARSE(e, "sin(90)"); + CHECK_EQ_EPS(e->Eval(), 1); + CHECK_PARSE(e, "cos(180)"); + CHECK_EQ_EPS(e->Eval(), -1); + CHECK_PARSE(e, "asin(1)"); + CHECK_EQ_EPS(e->Eval(), 90); + CHECK_PARSE(e, "acos(0)"); + CHECK_EQ_EPS(e->Eval(), 90); +} + +TEST_CASE(variable) { + Expr *e; + CHECK_PARSE(e, "Var"); + CHECK_TRUE(e->op == Expr::Op::VARIABLE); +} + +TEST_CASE(precedence) { + Expr *e; + CHECK_PARSE(e, "2 + 3 * 4"); + CHECK_TRUE(e->Eval() == 14); + CHECK_PARSE(e, "2 - 3 / 4"); + CHECK_TRUE(e->Eval() == 1.25); + CHECK_PARSE(e, "-3 + 2"); + CHECK_TRUE(e->Eval() == -1); + CHECK_PARSE(e, "2 + 3 - 4"); + CHECK_TRUE(e->Eval() == 1); +} + +TEST_CASE(errors) { + CHECK_PARSE_ERR("\x01", + "Unexpected character"); + CHECK_PARSE_ERR("notavar", + "'notavar' is not a valid variable, function or constant"); + CHECK_PARSE_ERR("1e2e3", + "'1e2e3' is not a valid number"); + CHECK_PARSE_ERR("_", + "'_' is not a valid operator"); + CHECK_PARSE_ERR("2 2", + "Expected an operator"); + CHECK_PARSE_ERR("2 + +", + "Expected an operand"); + CHECK_PARSE_ERR("( 2 + 2", + "Expected ')'"); + CHECK_PARSE_ERR("(", + "Expected ')'"); +} diff --git a/test/core/locale/test.cpp b/test/core/locale/test.cpp new file mode 100644 index 0000000..46ad9e1 --- /dev/null +++ b/test/core/locale/test.cpp @@ -0,0 +1,8 @@ +#include "harness.h" + +TEST_CASE(parseable) { + for(auto locale : Locales()) { + SetLocale(locale.Culture()); + } + CHECK_TRUE(true); // didn't crash +} diff --git a/test/core/path/test.cpp b/test/core/path/test.cpp new file mode 100644 index 0000000..9263956 --- /dev/null +++ b/test/core/path/test.cpp @@ -0,0 +1,245 @@ +#include "harness.h" + +using Platform::Path; + +#if defined(WIN32) +#define S "\\" +#define R "C:" +#define U "\\\\?\\C:" +#else +#define S "/" +#define R "" +#define U "" +#endif + +TEST_CASE(from_raw) { + Path path = Path::From("/foo"); + CHECK_EQ_STR(path.raw, "/foo"); +} + +#if defined(WIN32) || defined(__APPLE__) +TEST_CASE(equals_win32_apple) { + CHECK_TRUE(Path::From(R S "foo").Equals(Path::From(R S "foo"))); + CHECK_TRUE(Path::From(R S "foo").Equals(Path::From(R S "FOO"))); + CHECK_FALSE(Path::From(R S "foo").Equals(Path::From(R S "bar"))); +} +#else +TEST_CASE(equals_unix) { + CHECK_TRUE(Path::From(R S "foo").Equals(Path::From(R S "foo"))); + CHECK_FALSE(Path::From(R S "foo").Equals(Path::From(R S "FOO"))); + CHECK_FALSE(Path::From(R S "foo").Equals(Path::From(R S "bar"))); +} +#endif + +#if defined(WIN32) +TEST_CASE(is_absolute_win32) { + CHECK_TRUE(Path::From("c:\\foo").IsAbsolute()); + CHECK_TRUE(Path::From("\\\\?\\c:\\").IsAbsolute()); + CHECK_TRUE(Path::From("\\\\server\\share\\").IsAbsolute()); + CHECK_FALSE(Path::From("c:/foo").IsAbsolute()); + CHECK_FALSE(Path::From("c:foo").IsAbsolute()); + CHECK_FALSE(Path::From("\\\\?").IsAbsolute()); + CHECK_FALSE(Path::From("\\\\server\\").IsAbsolute()); + CHECK_FALSE(Path::From("\\\\?\\c:").IsAbsolute()); + CHECK_FALSE(Path::From("\\\\server\\share").IsAbsolute()); + CHECK_FALSE(Path::From("foo").IsAbsolute()); + CHECK_FALSE(Path::From("/foo").IsAbsolute()); +} +#else +TEST_CASE(is_absolute_unix) { + CHECK_TRUE(Path::From("/foo").IsAbsolute()); + CHECK_FALSE(Path::From("c:/foo").IsAbsolute()); + CHECK_FALSE(Path::From("c:\\foo").IsAbsolute()); + CHECK_FALSE(Path::From("\\\\?\\foo").IsAbsolute()); + CHECK_FALSE(Path::From("c:foo").IsAbsolute()); + CHECK_FALSE(Path::From("foo").IsAbsolute()); +} +#endif + +TEST_CASE(has_extension) { + CHECK_TRUE(Path::From("foo.bar").HasExtension("bar")); + CHECK_TRUE(Path::From("foo.bar").HasExtension("BAR")); + CHECK_TRUE(Path::From("foo.bAr").HasExtension("BaR")); + CHECK_TRUE(Path::From("foo.bar").HasExtension("bar")); + CHECK_FALSE(Path::From("foo.bar").HasExtension("baz")); +} + +TEST_CASE(file_name) { + CHECK_EQ_STR(Path::From("foo").FileName(), "foo"); + CHECK_EQ_STR(Path::From("foo" S "bar").FileName(), "bar"); +} + +TEST_CASE(file_stem) { + CHECK_EQ_STR(Path::From("foo").FileStem(), "foo"); + CHECK_EQ_STR(Path::From("foo" S "bar").FileStem(), "bar"); + CHECK_EQ_STR(Path::From("foo.ext").FileStem(), "foo"); + CHECK_EQ_STR(Path::From("foo" S "bar.ext").FileStem(), "bar"); +} + +TEST_CASE(extension) { + CHECK_EQ_STR(Path::From("foo").Extension(), ""); + CHECK_EQ_STR(Path::From("foo.bar").Extension(), "bar"); + CHECK_EQ_STR(Path::From("foo.bar.baz").Extension(), "baz"); +} + +TEST_CASE(with_extension) { + CHECK_EQ_STR(Path::From("foo.bar").WithExtension("baz").raw, "foo.baz"); + CHECK_EQ_STR(Path::From("foo").WithExtension("baz").raw, "foo.baz"); +} + +TEST_CASE(parent) { + Path path; + path = Path::From("foo" S "bar"); + CHECK_EQ_STR(path.Parent().raw, "foo" S); + path = Path::From("foo" S "bar" S); + CHECK_EQ_STR(path.Parent().raw, "foo" S); + path = Path::From(R S "foo" S "bar"); + CHECK_EQ_STR(path.Parent().raw, R S "foo" S); + path = Path::From(R S "foo"); + CHECK_EQ_STR(path.Parent().raw, R S); + + path = Path::From(""); + CHECK_TRUE(path.Parent().IsEmpty()); + path = Path::From("foo"); + CHECK_TRUE(path.Parent().IsEmpty()); + path = Path::From("foo" S); + CHECK_TRUE(path.Parent().IsEmpty()); + path = Path::From(R S); + CHECK_TRUE(path.Parent().IsEmpty()); +} + +#if defined(WIN32) +TEST_CASE(parent_win32) { + Path path; + path = Path::From(U S); + CHECK_TRUE(path.Parent().IsEmpty()); +} +#endif + +TEST_CASE(join) { + Path path; + path = Path::From("foo"); + CHECK_EQ_STR(path.Join(Path::From("bar")).raw, "foo" S "bar"); + path = Path::From("foo" S); + CHECK_EQ_STR(path.Join(Path::From("bar")).raw, "foo" S "bar"); + + path = Path::From(""); + CHECK_TRUE(path.Join(Path::From("bar")).IsEmpty()); + path = Path::From("foo"); + CHECK_TRUE(path.Join(Path::From("")).IsEmpty()); + path = Path::From("foo"); + CHECK_TRUE(path.Join(Path::From(R S "bar")).IsEmpty()); +} + +TEST_CASE(expand) { + Path path; + path = Path::From("foo"); + CHECK_EQ_STR(path.Expand().raw, "foo"); + path = Path::From("foo" S "bar"); + CHECK_EQ_STR(path.Expand().raw, "foo" S "bar"); + path = Path::From("foo" S); + CHECK_EQ_STR(path.Expand().raw, "foo"); + path = Path::From("foo" S "."); + CHECK_EQ_STR(path.Expand().raw, "foo"); + path = Path::From("foo" S "." S "bar"); + CHECK_EQ_STR(path.Expand().raw, "foo" S "bar"); + path = Path::From("foo" S ".." S "bar"); + CHECK_EQ_STR(path.Expand().raw, "bar"); + path = Path::From("foo" S ".."); + CHECK_EQ_STR(path.Expand().raw, "."); + path = Path::From(R S "foo" S ".."); + CHECK_EQ_STR(path.Expand().raw, U S); + path = Path::From(R S); + CHECK_EQ_STR(path.Expand().raw, U S); + + path = Path::From(R S ".."); + CHECK_TRUE(path.Expand().IsEmpty()); + path = Path::From(R S ".." S "foo"); + CHECK_TRUE(path.Expand().IsEmpty()); + path = Path::From(".."); + CHECK_TRUE(path.Expand().IsEmpty()); +} + +#if defined(WIN32) +TEST_CASE(expand_win32) { + Path path; + path = Path::From(R S "foo"); + CHECK_EQ_STR(path.Expand().raw, U S "foo"); + path = Path::From(U S "foo"); + CHECK_EQ_STR(path.Expand().raw, U S "foo"); +} +#endif + +TEST_CASE(expand_from_cwd) { + Path cwd = Path::CurrentDirectory().Expand(); + + Path path; + path = Path::From(R S "foo"); + CHECK_EQ_STR(path.Expand(/*fromCurrentDirectory=*/true).raw, + U S "foo"); + path = Path::From("foo" S "bar"); + CHECK_EQ_STR(path.Expand(/*fromCurrentDirectory=*/true).raw, + cwd.raw + S "foo" S "bar"); + path = Path::From(".." S "bar"); + CHECK_EQ_STR(path.Expand(/*fromCurrentDirectory=*/true).raw, + cwd.Parent().raw + "bar"); +} + +TEST_CASE(relative_to) { + Path base; + base = Path::From(R S "foo" S "bar"); + CHECK_EQ_STR(Path::From(R S "foo").RelativeTo(base).raw, + ".."); + base = Path::From(R S "foo" S "bar"); + CHECK_EQ_STR(Path::From(R S "foo" S "baz").RelativeTo(base).raw, + ".." S "baz"); + base = Path::From(R S "foo" S "bar"); + CHECK_EQ_STR(Path::From(R S "foo" S "bar" S "quux").RelativeTo(base).raw, + "quux"); + base = Path::From(R S "foo" S "bar"); + CHECK_EQ_STR(Path::From(R S "foo" S "bar").RelativeTo(base).raw, + "."); + + base = Path::From("foo"); + CHECK_TRUE(Path::From(R S "foo" S "bar").RelativeTo(base).IsEmpty()); + base = Path::From(R S "foo" S "bar"); + CHECK_TRUE(Path::From("foo").RelativeTo(base).IsEmpty()); +} + +#if defined(WIN32) +TEST_CASE(relative_to_win32) { + Path base; + base = Path::From("C:\\foo"); + CHECK_EQ_STR(Path::From("\\\\?\\C:\\bar").RelativeTo(base).raw, + "..\\bar"); + base = Path::From("C:\\foo"); + CHECK_EQ_STR(Path::From("c:\\FOO").RelativeTo(base).raw, + "."); + + base = Path::From("C:\\foo"); + CHECK_TRUE(Path::From("D:\\bar").RelativeTo(base).IsEmpty()); + CHECK_TRUE(Path::From("\\\\server\\share\\bar").RelativeTo(base).IsEmpty()); +} +#elif defined(__APPLE__) +TEST_CASE(relative_to_apple) { + Path base; + base = Path::From("/users/foo"); + CHECK_EQ_STR(Path::From("/Users/FOO").RelativeTo(base).raw, + "."); +} +#else +TEST_CASE(relative_to_unix) { + Path base; + base = Path::From("/users/foo"); + CHECK_EQ_STR(Path::From("/Users/FOO").RelativeTo(base).raw, + "../../Users/FOO"); +} +#endif + +TEST_CASE(from_portable) { + CHECK_EQ_STR(Path::FromPortable("foo/bar").raw, "foo" S "bar"); +} + +TEST_CASE(to_portable) { + CHECK_EQ_STR(Path::From("foo" S "bar").ToPortable(), "foo/bar"); +} diff --git a/test/debugtool.cpp b/test/debugtool.cpp new file mode 100644 index 0000000..aa66c1f --- /dev/null +++ b/test/debugtool.cpp @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// Our entry point for exposing various internal mechanisms. +// +// Copyright 2017 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" + +int main(int argc, char **argv) { + std::vector args = Platform::InitCli(argc, argv); + + if(args.size() == 3 && args[1] == "expr") { + std::string expr = args[2], err; + Expr *e = Expr::Parse(expr.c_str(), &err); + if(e == NULL) { + fprintf(stderr, "cannot parse: %s\n", err.c_str()); + } else { + fprintf(stderr, "%g\n", e->Eval()); + } + FreeAllTemporary(); + } else { + fprintf(stderr, "Usage: %s \n", args[0].c_str()); +//-----------------------------------------------------------------------------> 80 col */ + fprintf(stderr, R"( +Commands: + expr [expr] + Evaluate an expression. +)"); + } + + return 0; +} diff --git a/test/group/link/normal.png b/test/group/link/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..b3295bcfa81f43831cef8314582506fec98a01e0 GIT binary patch literal 4151 zcmeHLdr(t%7XIZn2?RDRf`E%!kWthL@+dM?il{t_7UZD>vY?PIiU{&hKpp`JYGrt6 z+FCI@1gljF2FSt&!Xx3bTBrglQUXXIp(!AQcjRS&>Y5Rt^Bv zc2#3QHA}_-4I?H1|3d44-E#O5OsL@1 zAv@hIbMz&yw-ONG(ls~xcjH_bL+rlQ8-C4Atj=(Lm<0isAJIw@q{hW!btXLaH{umn zHEW0lsuuMtt*tpAV@w+ya!|TlX36cG0L9uQCif>N1NerEva-<|xiO`HzQ66Hy+XVS zVtXq`6;Q9x6)yk2^aJLf&Z+?uLuFwB$-Aw^Tmcm(7oLK51G0yaGQgSx1U25E`SuWd zgF?LSqTy$LXaIkvH2Y=HC2MX{FhF;evao=0XJ}xJLU~F4g zU|SSlLm^%$F*?c=0_06fvzK#{8kf<^fvD-qY07Jlip)@;E5np&W2ON}-{6!nS<*R+ zfLwmuI!rNMMUi#2E($O;m1ZyB4X{naGd~pUC?`Q@!59YczX%UscsiIqU2(t`iEh8Iv1YxPQm{Qp5ZjmZqz3aE}*Z)P|>RbaYZ zlLQ#Y1xXH>k8dQy5c#ll$t|cJkBANLSoRMWk+aF{=zZ}m+M|)l^$rd?RuhF36mSW4 znaez8C|=5FotjBNh{Mm%PwERxT5PY?@H%=H1B;g|hT@KDGHFHoQtS-VLlDZV;a9!MPVK$|mCnXmzS|a_%lCRoffc zm~8%u0#tr*bxwwKBNLEv3bpYzw^7vSR6;hgi6s+PH|qC&V?Ye8Q$fHYU1s0->Nh?j z64sLr*t(6AqAzPgeI=iLKeZ@~SBmLNd}0&2wWybsOYi@7%L)C~HPY9t^ z=XKMU{@R%DglJ)!G~m<^tYxlLVb!m<&Ll>9M5heuR@u{+cN9(s-bO!gEqz?YJMb(< zt~(d36?CW5>%)^O%|AO6cOpU}Ok7b%R`fQp-p!+xf0Wc<>&t4uYDR0BFY@)dyX?G9L>*cO)}{y6 zCn6$Ll}MS29};O%=H}hL!ps(!GP571LR!)QuBf5y-~~JNu@t0Yc=^*^MZ-Z|l5tn& z%+9*S*^1-Rig2k_=TbR(e#V_8i%IggE$h{aXa6GWyX7w`{m>1u-|?eTd}DeqoV@^W ze7G&7c*esrZ=IE`Ai=V(-+aA(?}D*GQK6M>PXZ0ZJafho*qb4BdLuo2I5Zg3lKn6f zXDsCBm-wy<@2c=>7*N86(@DulKL-Hmus=E4&v`=`pc1O< z5Fv-Jyf#5FOA^DoihuJ;xh9;rL!pceh`ei9B@iIJGysJM-aL!EY|+eeZi_A`{0eCa z4>_ZN+}M|GR&ku6MvqdO!Y~Tm;<9nG3mZ#MhaAQLrrC;J;p;O}^hZuT-8ZLEW<~M$s{lv10v^t;!wr0 zggq58y8^IIi=Wg2sw5&U;_u~7hJX>=isP*j2F*>8OxJ<=ZV_;c zRZ%5tDj1>SSlC$wiRZUW8 zVbh60ZTh|3T&(C@SF-SBaizl;$rDH<_=cooH)n66cyGo#E8h+P*+~-)2gh1`vMt z$^izrSn&|cv*Zp0Tz_7dJngeF)d-O7B!1_nZMePh*S7!|-?Eley6SIWclnO@#H@oU zuA3p?X347e+LhN4K`AMPY#*F_4@p`MwwCxBnKKx3<^U_PyYkf${zMlUUeZq?FaC3< hV#Pso1xS{#P~G0xs?P;`kv{~$K1WxF51>gDwC7 literal 0 HcmV?d00001 diff --git a/test/group/link/normal.slvs b/test/group/link/normal.slvs new file mode 100644 index 0000000..97ba795 --- /dev/null +++ b/test/group/link/normal.slvs @@ -0,0 +1,465 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000003 +Group.type=5300 +Group.order=2 +Group.name=rect-v20 +Group.activeWorkplane.v=00010000 +Group.color=00646464 +Group.skipFirst=0 +Group.meshCombine=2 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00010000 0 + 2 00010001 0 + 3 00010020 0 + 4 00020000 0 + 5 00020001 0 + 6 00020020 0 + 7 00030000 0 + 8 00030001 0 + 9 00030020 0 + 10 00040000 0 + 11 00040001 0 + 12 00040002 0 + 13 00050000 0 + 14 00050001 0 + 15 00050002 0 + 16 00060000 0 + 17 00060001 0 + 18 00060002 0 + 19 00070000 0 + 20 00070001 0 + 21 00070002 0 + 22 80020000 0 + 23 80020001 0 + 24 80020002 0 +} +Group.impFile=/TESTROOT/rect_v20.slvs +Group.impFileRel=rect_v20.slvs +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=80030000 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=80030001 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=80030002 +AddParam + +Param.h.v.=80030003 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=80030004 +AddParam + +Param.h.v.=80030005 +AddParam + +Param.h.v.=80030006 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030005 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030008 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000b +Entity.point[1].v=8003000c +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000b +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000e +Entity.point[1].v=8003000f +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030011 +Entity.point[1].v=80030012 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030013 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030014 +Entity.point[1].v=80030015 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030014 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030015 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030017 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030018 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030018 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + diff --git a/test/group/link/normal_v20.slvs b/test/group/link/normal_v20.slvs new file mode 100644 index 0000000..f06c81d --- /dev/null +++ b/test/group/link/normal_v20.slvs @@ -0,0 +1,463 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000003 +Group.type=5300 +Group.order=2 +Group.name=rect-v20 +Group.activeWorkplane.v=00010000 +Group.opA.v=00000002 +Group.color=00646464 +Group.skipFirst=0 +Group.meshCombine=2 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00010000 0 + 2 00010001 0 + 3 00010020 0 + 4 00020000 0 + 5 00020001 0 + 6 00020020 0 + 7 00030000 0 + 8 00030001 0 + 9 00030020 0 + 10 00040000 0 + 11 00040001 0 + 12 00040002 0 + 13 00050000 0 + 14 00050001 0 + 15 00050002 0 + 16 00060000 0 + 17 00060001 0 + 18 00060002 0 + 19 00070000 0 + 20 00070001 0 + 21 00070002 0 + 22 80020000 0 + 23 80020001 0 + 24 80020002 0 +} +Group.impFile=Z:\TESTROOT\rect_v20.slvs +Group.impFileRel=rect_v20.slvs +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=80030000 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=80030001 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=80030002 +AddParam + +Param.h.v.=80030003 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=80030004 +AddParam + +Param.h.v.=80030005 +AddParam + +Param.h.v.=80030006 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030005 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030008 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000b +Entity.point[1].v=8003000c +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000b +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000e +Entity.point[1].v=8003000f +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030011 +Entity.point[1].v=80030012 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030013 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030014 +Entity.point[1].v=80030015 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030014 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030015 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030017 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030018 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030018 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + diff --git a/test/group/link/normal_v22.slvs b/test/group/link/normal_v22.slvs new file mode 100644 index 0000000..fb39ea2 --- /dev/null +++ b/test/group/link/normal_v22.slvs @@ -0,0 +1,465 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000003 +Group.type=5300 +Group.order=2 +Group.name=rect-v20 +Group.activeWorkplane.v=00010000 +Group.color=00646464 +Group.skipFirst=0 +Group.meshCombine=2 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00010000 0 + 2 00010001 0 + 3 00010020 0 + 4 00020000 0 + 5 00020001 0 + 6 00020020 0 + 7 00030000 0 + 8 00030001 0 + 9 00030020 0 + 10 00040000 0 + 11 00040001 0 + 12 00040002 0 + 13 00050000 0 + 14 00050001 0 + 15 00050002 0 + 16 00060000 0 + 17 00060001 0 + 18 00060002 0 + 19 00070000 0 + 20 00070001 0 + 21 00070002 0 + 22 80020000 0 + 23 80020001 0 + 24 80020002 0 +} +Group.impFile=/TESTROOT/rect_v20.slvs +Group.impFileRel=rect_v20.slvs +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=80030000 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=80030001 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=80030002 +AddParam + +Param.h.v.=80030003 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=80030004 +AddParam + +Param.h.v.=80030005 +AddParam + +Param.h.v.=80030006 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030005 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030008 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000b +Entity.point[1].v=8003000c +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000b +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000e +Entity.point[1].v=8003000f +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030011 +Entity.point[1].v=80030012 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030013 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030014 +Entity.point[1].v=80030015 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030014 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030015 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030017 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030018 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030018 +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + diff --git a/test/group/link/rect_v20.slvs b/test/group/link/rect_v20.slvs new file mode 100644 index 0000000..3bebad4 --- /dev/null +++ b/test/group/link/rect_v20.slvs @@ -0,0 +1,510 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00070014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00070001 +Entity.point[1].v=00070002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040001 +Constraint.ptB.v=00050002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050001 +Constraint.ptB.v=00060002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00060001 +Constraint.ptB.v=00070002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000004 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00070001 +Constraint.ptB.v=00040002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000005 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000006 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000007 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000008 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00070000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/group/link/test.cpp b/test/group/link/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/group/link/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/group/translate_asy/normal.png b/test/group/translate_asy/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..c9b4e4ec831e041d2e339d3a4d1cc6de6951bde5 GIT binary patch literal 8508 zcmaiac{r49`~S>X8pbkX8EaXJXl5kKSSq^~2~pEnVnoO?Egm9a24fphvhO@mGBMdn z3@WlqM)s{tl0-$(@V)6h-lz9_yx-sNk2#K+>z@0(uk$*W&v~AoJI2c5I6to>F9-zU zKY7B$8U%viw?9yBV5QM5Z43mG1)Vf8JR6X;I6mz++CEI`pT6-eH1q0#JV|;+^npCo z+XL2QTXHqe6%JRf%mc4MO)_fxpz=z0q(NVM#)9H4vZ}CTW%$HHq(_G29iao0}oH>Jj2UP_`bepIQ{ zJICSo)p5TP+0$F@AcymEDjaTL|oDg5MaH zR+?Egvh+f9pE~}_-C=ZX{2FhT0(~kg#6v=R>d2Cy_M(d~~Nq=mabLB#lqmRE4Y<}OBgnX#lP!%%cureWM`?fuUF5Ln7ZD;J+0LwuYj zo}R?s{N8y}jJdCrfY_0u9?k{2lg@zrAG+a8R?m3e%RQ_d*mJ4mS66Ewegi~`SIx*W(KNfDlI;j~oWXF(zyyke@s`CeP`} zpL?a<@40B73peZsEl6O`K)^E+n7plueZ)T&-!{y)?RGBw*3r~;?+G)}pTj|S6SCKJ zw0jr#1?f|BIeJGvh&DzucNv_*q8qlyqL!lr7X{AKwN@9)JqVl;!juF6@h8HJ{ zi?L?R-e9#wXE2=MD02#ln?Z3!^_ys(q5$ z^abuWB7hepU-nVPDFZH7{S`xkNu7K`A$TkPVq!h z6ATwj41fDkSPBn0FfcgZ z76P+#opOpFlFZmg&r{&u_#UvBue{?gqZ*+9JIs&#JzGcqzEV-xTLo4haR{kQQ%F zWLGo8RHWxz8if&-#fLL_KSrT(BHt0N79e{rwYNNg%879ZXn3|)~c`sP(o<`gILP~8tBsfHmQGXjE2#ZGUTw847yIfcT%>;HRgsCp4o? zdC+sL0`8=|r`&alPe9}s4G!(3*H8?Sz>oa;foZ$Ars8Oj4yY}W-YkDGbT6m;q0ATN zoXEgBZ7`zF|Lz}|=fB%9HIOLFa4)@mt637=Q?Z|qG30=fkfZRf-ky^NDG9B_TwVk)u;&VR~MOv+0jhaO| z@7q-5=qqJUM5RNnQB`Bddy)PRv~x zqyM5;oI8rPT4tF$ZLGS}Bu34xf~!)LlHyUTr4~02%*;hA#OVBm;{F z1EXbHZQU=HhP`{X^i=f$STZz^DM_-v84?n*vANmAfp))Vz4Dnu39p}6ACU3!{rmTe zLvED>9Y4;LY!@=`xG*kQo@#YKVqkU`PC*|^vM8lX3fV;n-MW<^g>QBWAomdd1UF@yvT`U#1*_em)jS%z(b4ne`+Ojj)EM7+9>E7e22X%x5ZiH?ngN?q?`}t+Tks?A7VDJz|GruX<)Z(S5K{@vNbt zft}v%cBVnJ-ep*VHvTy{!^Q!e?4eM)zFy(Wwmqz5JtTd>14BrcK&#^nD0X}$a_V*w zhvI1InXVHI0A45&gYLb(y;rDHmJjZ=7dXHu%m%UojT8$l>FjbtbadUGv58=04$LQ6 zj4oGcV9F{G=Lc+axx9h_+%gLVjY%Ku)vm;<%h8DL$EYbhXpb>f+0E?DqwCLli?hKt z`1dZsNQ@A^?_m(6U^SiRTC&q3i;oZZpLtKfLmxLIe7B`XKQEczVxL z;EJT7$ZHBYT34AA+`2Trg}3E^<{mR~)lG+=_<>EMz!NIT+#*kc+ns)ms)? zwDV1sp2go3q>WqP4GsDs7hi$ZN4#2rWeq)3sG(^>l+LcM4`uwb5y&%8H1uH+sa=P_ z{8q1O;ff$L!VEkGqJy}*Gn~X;@?gvq>9QmoWF`e%kqm6ah$_T`R>GGoe{p6U$&pdD z9K||GZU0T4F254ft5_*{KKS6lgJK*dgF}>SgU3=9LbSJPR>2^dY>NjqwSXfNb>&sq z!)-0p>hF{4>@^|uA`ng>$Mh#*x9ZpWIy*Z@gGwGrU}E>v371+vVjnCjAKhOjmdb-+ z&LHJzCmhCEfY-NXNQn=GhSvGC3w05ic_RkD*IV-YdzE3^PLFK9EozC}z^)dBL}fz@OefW9L>OzL!1)Yk93^}hZZ<(#-dpjzX`w%rCNSsw9^@;a)o@08PA;0Xk{{HQL-h6hbT@nEJ< z5LCf9adGd%k;_491htsHnZ(r8R6&`vIx1je2z4&I`hlQWGqF1Yd=EIHg0j%X4`t0A zNc)H{^i?p!v;NTXn&y=am(h*KLz73O1+Ivh!6xhOQ_(E*r|Z|)-@YYTo6QHUZ*KB8 zIqvP;#QUg*bBU#;PuUTrs^=XX`msRI!lf=eHUpT+XNi`YD#kJ|j1UC?-F#nVZHjM^ zV@qrq6kVH`&Qq#T`B9FR)**?mCHrB=S^|YLYiA_g=u-l2inSEmK752|4IF=m3>dd3 zeT4`0c^WC_>T18M^nJB+`g74WwQ#QV^zC3Xxuzi24e zS2QsH9n;tJOe>1mXMWOSyg&-X+SEyYuA|b2$vobZKF<^=iD!6HHSt!Ug^Jpg@3sR;Lgy>n_>kQwXYP{TuIIIYNiK$1 zC|v8gg}aDPZ}y}z3IYqcaBX>k!=YOnlU0KG+CAxNt&jH7N95@qj>Y8UM6fS!{&!m{ z>+~zi`J%^vCq6qYNs7Q#OU)(;vH{OlxDuP9XNmHf<#5yI^z z16vpEC#Jp*X<9vVcCR(T&vTE4FWfG7@UI>dSV_+X@_PV3eI#XNM|B-m7e{Np0iP^aNFsgMlPdM03zUIngV|uq_^vww8WvXr$0< zp2YBG26|0mMR0FS2!<~rn3s$eDl~f}oK0_YA~gt$=`pVXAt_LdvuQW2IqX)kF6u7taVYUR?BW^S@HN9K+;1K#Dy+9NKlT@fw0l8#&o~H6ns3} zJNseHC)7Jt_x@lu>~Z(NNeJwRi3fP>I05|k5&vCNSO~1P-J7|P zyHTdU`GZc7ni3*&nZLg1;pbfSb9ry@7Av=(GS+a|$I?h7(Gn46iI{k}YAL<>y-h~B zFv&tcH0Q#7cGDL7%Dgde{^5gY@2#!}(9w(Bu-cQ47b@y)fl6b2vsd2;TpPoH5OD)O zt<#H)rLA8Tt8!p64|;my9@a2kWCRB;}g*OMOQK1hyB6$a0Ucf@y8z!uEH;=?`POP^1H}AEz6tj(LoyL^sGuDFJ)Cfs00pz{e8nTkNhji?tljNU`V}I$a@IMV+-(;=T zY^|>~ZThW_a*ETr$z0DXCkvZ`PAl9eB?6seQ8ORX;3R+_1?-T;y|RDLR^K-L#MlkY zc@Bkhq2^mWgN_s}_rf9YyNoc(_tr-P9XP(45006w(|4@RR%;pD(`T%{vqxxgJ2J z!+_RXOZYtKw$8w5))2w3!uDx)dss|_sDX=$`H9;HuWMy;-2+PKm*yUSm+6}-(FjSk z5RbkHY`GxLL~mIqvpC`L^1K1z3{Zu=-32QpyyPVT$MVbn@~Rmge3icjn$L1Dh(Uo$ zFLA?;K~W&00^MV1`nx`Wqh=dJT=&~@+iIL(s42(LE!I+v)0OzlA*WxBtM}X9Cq%vP zlEPyTC}glz3di+)LL183B<(X57@)AsCpL5Z=w7XT1#Few+o!3`x~hG@?YIpb{u<(= zwuLcD7?%qhZ}Y9t59e|bh#CqyY6OGWdd#?|YvT7jHJ07Rd^_o8v7DTo*JVyk)@A_q z-5AN47;=14N|ACMHvMcwmF+TDeK9a#W2Ri_)D)uQ{v(fig_myaOZ%#9Yl9)y1pe>!Xz~gwlQHvtm|#I}GvH;w8{&c~3nu`Y#@+u>CHKbC|J>xJubH zt(4N3PJHm-LCsrTX@QdY*v9H$K)fV zeisIDopGW=v0CASi|$~`)zLd%bw1)z5+z}rQd_fJIJ0R-*N-fqpn%(5QujJ{hcp{9 z<|Fxu+p^hv;f~Xy%vRYY`zXl}iSKsjGa9_0BoW22v+Y{}2DCem+=TIoHB7rh_$ zXWSv;6da9-`5v0Lr!Xp0j)wH*UbLIG@h+#38`^pp^uE?>i6 zw{4OU>1-_<%G2E^L^+Wn1|HX_D&^K{u$%Gpi>HBMgCPx&altjax+gu}o}9$s!C?KeH1_q6w?J)kI~SVN=3^YGH@2dilMscwB83Y&tUB|y za`agU*QC-nMrx}~sJxcr%)tWy7 zzL)TsCpu%+0WbPIEilDCg2iVHEr=RBaR^tRqR6uu}RwKwzVH+GvoCoJ~)omIR4t5u)r<%2+KS4HEiHKwVDmnG1EuJZ-8+s*-BXH**Zk$bXx-q_b zvdJ^8Io7*LU*C5y)Ma&&zPrM1ET1xruHmqKQP(8>)GZW#6O;7W|~_N`x-Q-$^sM48Bo+^9t58qQw! zT5;2^8`1TlDYht_W>!`G+=g<71g*WONAJSAeS+z-tH@<6kP2R;sd;IxZisa9;m(eG z<8I_OZn&p5hJFkVT3f5Ld9_f{g+G&YYJpZiDU6d#Z;mAfbbcAzZ`F9ych&t#m)qSl zm(}gu9xGYgc*g!{XQAyz_w3K@m)!iiS$447A_nDFzPgUCz(vTGJ}N({d?AES*RM`v z_u}bpNd0Xyn|dO)1Xcm0u~%F15-0-3jc(`LZHG>47TSI;*h?7CI51c5!>?M?;JP}(zc2WkL5KKA5hanX%0wm7Hv)YUPZghP7Fq*($szYN`K*1n2Un>01fzH3U z8l~LFyGgzdQc7tId2TW5yn)GHUfxTY+%xo4{>pAmk^v`jxwhTNzJbNljxm}60@Wj` zR$fUR|9ox+=C?ATs7*HAjSisP4A8gv%`j;GL;U~{e{@$La)=F39NQ{WSAS&6qI);u zU+(Zfwd{#Khv_0qnvwvh4_ws>5DRVZ45Gf|L60a_%IYue2f7n$vz#kXolpKtPw7;Y zq&qiE*R5g-JE8t1h7pzzutKU+YD>b}ojV!=yup9bPB$o3U4 zN8s;d8pcLv)!a#Dgz1RWqFd^CE8xJuI9#moIGws0r1r87`~vt{PHd9rP!7Hj zu(Y;X8D?}8Aix-6k#{c{_@w%R09pl3>GMp@)rr_kmzB&PMC!XkUrC~)JJxCSKy8^H zh?D~kaA!9Czo|8t-HdS5!peue9+8YNrufv5vp&ir)<-hWlzZcSz~g+NMz?eOlAADu z%mC=6Lw7QVgfsR7v;Tsr5J%&wnpMa`lH=AJMxgvOcHEZ(Z0$uJ+`1Qw1`!k&i35EW zI0y*lrA4lW7&ok9*z@0mTj;Blv+e9o`c2505=q zbzd0#<<8*-EMAZ=pYk8C=B%Qe5R0$A03v<^y6rAv$D;mSt{SOhxOYm$)4d>({7uHk zXr;9C@A_f`x(xaOj%Ov-f7HqWto|^F+spgfVbwBdg{^{&6x?x~%tb4au~VR!UvA?# z`{1!VqR15o1EppE;d(p%_S+qj-ZR-11)rXBX*558l2;rmjwiEeBnS^W&mnc@Bp%EK zM26DPTbCQpMXj{e@dkYCcA#sR?jnHSOZDJw`vgRK0N$wj$tXEzKU8EVZsvB(@q0ol zdY7vakR~_0*r9{WvxC6W&WmXjdh>c}1rWg7qjjs|M49#@aWtU> z1QeKF2b#tr^SQd7V~k{TALh?g%iT$LM?ip?_J46|rWGOEI`n9$J!%6?QqcUO+%he5 z#dj_j1bS!llaKi`Y2<#w89&{x*?k%th8NlvxcJWMhWm|6Cre;{U_KtpSNJjlKAOzM zTrA#K?vbJ0-sZ(~-xGpz+QWm%*bAMT_M;R5`1y6;-M)gG04w3>;(?oDWayJkkf5tn z61nQuDii~nk|aaC*j=_;0Feg``xJie*vMFUC=(szJ?y({Cn!Yr;9HMKDE8<^9#>V! z3o{pnfF!m9pOt*b#qZ<|lLyVA0tp&@NAV!pdlYwi>9c&?%eX-W8={ne3m@p=Xc$=& zn5cFLl;jsLRG1)#7yX&;Z`ylBj8IcZ=TCG=$qAt&g=;@_`8OrTUMQycjV`g{90`H;5a0i%8FTkHn8ED8}JkV+boIv>m;O66>5nlr}*#+LZ2F%pqTs|Mil zjl3n(kTu=7ONI6nhe8-?#1#uOvi(pxh}jbJD{8AdU(lnk465`ZOVGS#9({gu)gBHS zA&h_TYP@-E!NLsWVFMo3s5CYQ8%P1J19HXvXw3nGW)Q#AAwH4CU5;e!K?5@%d@R4b zGWjbUDMu#|xi!pYvm%Ch&}meFlGZuy4D`@*Q+50q99Mx?0@k+Q1t6Qu+@b(oDUbnx z>F7U{hU-BoQg!X;sJ(yi-8*S0Zut|owkwG97!O*zOk literal 0 HcmV?d00001 diff --git a/test/group/translate_asy/normal.slvs b/test/group/translate_asy/normal.slvs new file mode 100644 index 0000000..ec2bf7f --- /dev/null +++ b/test/group/translate_asy/normal.slvs @@ -0,0 +1,2088 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000003 +Group.type=5100 +Group.order=2 +Group.name=extrude +Group.opA.v=00000002 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.meshCombine=2 +Group.predef.entityB.v=80020000 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00040000 1002 + 2 00040001 1002 + 3 00040002 1002 + 4 00040000 1001 + 5 00040001 1001 + 6 00040002 1001 + 7 00040000 1004 + 8 00040001 1003 + 9 00040002 1003 + 10 00050000 1002 + 11 00050001 1002 + 12 00050002 1002 + 13 00050000 1001 + 14 00050001 1001 + 15 00050002 1001 + 16 00050000 1004 + 17 00050001 1003 + 18 00050002 1003 + 19 00060000 1002 + 20 00060001 1002 + 21 00060002 1002 + 22 00060000 1001 + 23 00060001 1001 + 24 00060002 1001 + 25 00060000 1004 + 26 00060001 1003 + 27 00060002 1003 + 28 00070000 1002 + 29 00070001 1002 + 30 00070002 1002 + 31 00070000 1001 + 32 00070001 1001 + 33 00070002 1001 + 34 00070000 1004 + 35 00070001 1003 + 36 00070002 1003 + 37 80020000 1002 + 38 80020000 1001 + 39 80020001 1002 + 40 80020002 1002 + 41 80020001 1001 + 42 80020002 1001 + 43 80020002 1003 + 44 00000000 1001 + 45 00000000 1002 +} +AddGroup + +Group.h.v=00000004 +Group.type=5201 +Group.order=3 +Group.name=translate +Group.opA.v=00000003 +Group.valA=2.00000000000000000000 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.meshCombine=2 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 80030001 0 + 2 80030002 0 + 3 80030003 0 + 4 80030004 0 + 5 80030005 0 + 6 80030006 0 + 7 80030007 0 + 8 80030008 0 + 9 80030009 0 + 10 8003000a 0 + 11 8003000b 0 + 12 8003000c 0 + 13 8003000d 0 + 14 8003000e 0 + 15 8003000f 0 + 16 80030010 0 + 17 80030011 0 + 18 80030012 0 + 19 80030013 0 + 20 80030014 0 + 21 80030015 0 + 22 80030016 0 + 23 80030017 0 + 24 80030018 0 + 25 80030019 0 + 26 8003001a 0 + 27 8003001b 0 + 28 8003001c 0 + 29 8003001d 0 + 30 8003001e 0 + 31 8003001f 0 + 32 80030020 0 + 33 80030021 0 + 34 80030022 0 + 35 80030023 0 + 36 80030024 0 + 37 80030027 0 + 38 80030028 0 + 39 80030029 0 + 40 8003002a 0 + 41 8003002b 0 + 42 8003002c 0 + 43 8003002d 0 + 44 80030001 1 + 45 80030002 1 + 46 80030003 1 + 47 80030004 1 + 48 80030005 1 + 49 80030006 1 + 50 80030007 1 + 51 80030008 1 + 52 80030009 1 + 53 8003000a 1 + 54 8003000b 1 + 55 8003000c 1 + 56 8003000d 1 + 57 8003000e 1 + 58 8003000f 1 + 59 80030010 1 + 60 80030011 1 + 61 80030012 1 + 62 80030013 1 + 63 80030014 1 + 64 80030015 1 + 65 80030016 1 + 66 80030017 1 + 67 80030018 1 + 68 80030019 1 + 69 8003001a 1 + 70 8003001b 1 + 71 8003001c 1 + 72 8003001d 1 + 73 8003001e 1 + 74 8003001f 1 + 75 80030020 1 + 76 80030021 1 + 77 80030022 1 + 78 80030023 1 + 79 80030024 1 + 80 80030027 1 + 81 80030028 1 + 82 80030029 1 + 83 8003002a 1 + 84 8003002b 1 + 85 8003002c 1 + 86 8003002d 1 + 87 80030001 1000 + 88 80030002 1000 + 89 80030003 1000 + 90 80030004 1000 + 91 80030005 1000 + 92 80030006 1000 + 93 80030007 1000 + 94 80030008 1000 + 95 80030009 1000 + 96 8003000a 1000 + 97 8003000b 1000 + 98 8003000c 1000 + 99 8003000d 1000 + 100 8003000e 1000 + 101 8003000f 1000 + 102 80030010 1000 + 103 80030011 1000 + 104 80030012 1000 + 105 80030013 1000 + 106 80030014 1000 + 107 80030015 1000 + 108 80030016 1000 + 109 80030017 1000 + 110 80030018 1000 + 111 80030019 1000 + 112 8003001a 1000 + 113 8003001b 1000 + 114 8003001c 1000 + 115 8003001d 1000 + 116 8003001e 1000 + 117 8003001f 1000 + 118 80030020 1000 + 119 80030021 1000 + 120 80030022 1000 + 121 80030023 1000 + 122 80030024 1000 + 123 80030027 1000 + 124 80030028 1000 + 125 80030029 1000 + 126 8003002a 1000 + 127 8003002b 1000 + 128 8003002c 1000 + 129 8003002d 1000 +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00070014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030000 +AddParam + +Param.h.v.=80030001 +AddParam + +Param.h.v.=80030002 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80040000 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80040001 +Param.val=2.50000000000000000000 +AddParam + +Param.h.v.=80040002 +Param.val=-2.50000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00070001 +Entity.point[1].v=00070002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030001 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.point[1].v=80030003 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030004 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030005 +Entity.point[1].v=80030006 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030007 +Entity.type=5001 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actNormal.vx=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030005 +Entity.point[1].v=80030002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.point[1].v=80030003 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000b +Entity.point[1].v=8003000c +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000b +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000e +Entity.point[1].v=8003000f +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=5001 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actNormal.vy=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000e +Entity.point[1].v=8003000b +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.point[1].v=8003000c +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030013 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030014 +Entity.point[1].v=80030015 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030014 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030015 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030016 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030017 +Entity.point[1].v=80030018 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030017 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030018 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030019 +Entity.type=5001 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030017 +Entity.point[1].v=80030014 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001b +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030018 +Entity.point[1].v=80030015 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001c +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003001d +Entity.point[1].v=8003001e +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001d +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001e +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001f +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030020 +Entity.point[1].v=80030021 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030020 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030021 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030022 +Entity.type=5001 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actNormal.vy=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030023 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030020 +Entity.point[1].v=8003001d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030024 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030021 +Entity.point[1].v=8003001e +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030027 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030028 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030028 +Entity.type=2010 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030029 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003002a +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003002a +Entity.type=2010 +Entity.construction=1 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003002b +Entity.type=11000 +Entity.construction=1 +Entity.point[0].v=8003002a +Entity.point[1].v=80030028 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003002c +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003002a +Entity.actPoint.z=10.00000000000000000000 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003002d +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=80030028 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040001 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040002 +Entity.point[1].v=80040003 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040002 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040003 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040004 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040005 +Entity.point[1].v=80040006 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040005 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040007 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actNormal.vx=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040008 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040005 +Entity.point[1].v=80040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040006 +Entity.point[1].v=80040003 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004000b +Entity.point[1].v=8004000c +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000b +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000c +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000d +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004000e +Entity.point[1].v=8004000f +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000e +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000f +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040010 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actNormal.vy=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040011 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004000e +Entity.point[1].v=8004000b +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040012 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004000f +Entity.point[1].v=8004000c +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040013 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040014 +Entity.point[1].v=80040015 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040014 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040015 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040016 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040017 +Entity.point[1].v=80040018 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040017 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040018 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040019 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040017 +Entity.point[1].v=80040014 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001b +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040018 +Entity.point[1].v=80040015 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001c +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004001d +Entity.point[1].v=8004001e +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001d +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001e +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001f +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040020 +Entity.point[1].v=80040021 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040020 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040021 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040022 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actNormal.vy=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040023 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040020 +Entity.point[1].v=8004001d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040024 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040021 +Entity.point[1].v=8004001e +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040025 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80040026 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040026 +Entity.type=2010 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040027 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80040028 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040028 +Entity.type=2010 +Entity.construction=1 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040029 +Entity.type=11000 +Entity.construction=1 +Entity.point[0].v=80040028 +Entity.point[1].v=80040026 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004002a +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.z=10.00000000000000000000 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004002b +Entity.type=5003 +Entity.construction=0 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040057 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040058 +Entity.point[1].v=80040059 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040058 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040059 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004005b +Entity.point[1].v=8004005c +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005b +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005c +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005d +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actNormal.vx=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005e +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004005b +Entity.point[1].v=80040058 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005f +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004005c +Entity.point[1].v=80040059 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040060 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040061 +Entity.point[1].v=80040062 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040061 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040062 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040063 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040064 +Entity.point[1].v=80040065 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040064 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040065 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040066 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actNormal.vy=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040067 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040064 +Entity.point[1].v=80040061 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040068 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040065 +Entity.point[1].v=80040062 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040069 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004006a +Entity.point[1].v=8004006b +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006a +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006b +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006c +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004006d +Entity.point[1].v=8004006e +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006d +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006e +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006f +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040070 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004006d +Entity.point[1].v=8004006a +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040071 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004006e +Entity.point[1].v=8004006b +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040072 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040073 +Entity.point[1].v=80040074 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040073 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040074 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040075 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040076 +Entity.point[1].v=80040077 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040076 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040077 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040078 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actNormal.vy=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040079 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040076 +Entity.point[1].v=80040073 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040077 +Entity.point[1].v=80040074 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007b +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8004007c +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007c +Entity.type=2010 +Entity.construction=1 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007d +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8004007e +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007e +Entity.type=2010 +Entity.construction=1 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007f +Entity.type=11000 +Entity.construction=1 +Entity.point[0].v=8004007e +Entity.point[1].v=8004007c +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040080 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040081 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040001 +Constraint.ptB.v=00050002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050001 +Constraint.ptB.v=00060002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00060001 +Constraint.ptB.v=00070002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000004 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00070001 +Constraint.ptB.v=00040002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000005 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000006 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000007 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000008 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00070000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Surface 00000001 00646464 8004002a 1 1 +SCtrl 0 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000010000000827 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000000000 -5.00000000010000000827 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000010000000827 -5.00000000010000000827 10.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000001 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +TrimBy 00000004 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 00000007 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 0000000a 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +AddSurface +Surface 00000002 00646464 8004002b 1 1 +SCtrl 0 0 10.00000000010000000827 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000010000000827 -5.00000000010000000827 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 -5.00000000000000000000 -5.00000000010000000827 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000002 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +TrimBy 00000005 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +TrimBy 00000008 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000b 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000003 00646464 80040010 1 1 +SCtrl 0 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000001 1 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +TrimBy 00000002 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +TrimBy 00000003 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +TrimBy 0000000c 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000004 00646464 80040019 1 1 +SCtrl 0 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000004 1 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +TrimBy 00000005 0 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000006 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 00000003 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000005 00646464 80040022 1 1 +SCtrl 0 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000007 1 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 00000008 0 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000009 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 00000006 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000006 00646464 80040007 1 1 +SCtrl 0 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000a 1 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 0000000b 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000c 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +TrimBy 00000009 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000007 00646464 80040080 1 1 +SCtrl 0 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 20.00000000010000178463 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000000000000000 -0.00000000010000000827 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 20.00000000010000178463 -0.00000000010000000827 5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000d 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000010 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 00000013 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 00000016 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +AddSurface +Surface 00000008 00646464 80040081 1 1 +SCtrl 0 0 20.00000000010000178463 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 20.00000000010000178463 -0.00000000010000000827 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 5.00000000000000000000 -0.00000000010000000827 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000e 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +TrimBy 00000011 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +TrimBy 00000014 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +TrimBy 00000017 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddSurface +Surface 00000009 00646464 80040066 1 1 +SCtrl 0 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000d 1 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 0000000e 0 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +TrimBy 0000000f 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000018 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddSurface +Surface 0000000a 00646464 8004006f 1 1 +SCtrl 0 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000010 1 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000011 0 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +TrimBy 00000012 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 0000000f 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddSurface +Surface 0000000b 00646464 80040078 1 1 +SCtrl 0 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000013 1 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 00000014 0 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +TrimBy 00000015 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 00000012 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddSurface +Surface 0000000c 00646464 8004005d 1 1 +SCtrl 0 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000016 1 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 00000017 0 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +TrimBy 00000018 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000015 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddSurface +Curve 00000001 1 1 00000001 00000003 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +AddCurve +Curve 00000002 1 1 00000002 00000003 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000003 1 1 00000003 00000004 +CCtrl 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000004 1 1 00000001 00000004 +CCtrl 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +AddCurve +Curve 00000005 1 1 00000002 00000004 +CCtrl 0 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000006 1 1 00000004 00000005 +CCtrl 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000007 1 1 00000001 00000005 +CCtrl 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +AddCurve +Curve 00000008 1 1 00000002 00000005 +CCtrl 0 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000009 1 1 00000005 00000006 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 0000000a 1 1 00000001 00000006 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +AddCurve +Curve 0000000b 1 1 00000002 00000006 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 0000000c 1 1 00000006 00000003 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 0000000d 1 1 00000007 00000009 +CCtrl 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +AddCurve +Curve 0000000e 1 1 00000008 00000009 +CCtrl 0 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 0000000f 1 1 00000009 0000000a +CCtrl 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000010 1 1 00000007 0000000a +CCtrl 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +AddCurve +Curve 00000011 1 1 00000008 0000000a +CCtrl 0 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000012 1 1 0000000a 0000000b +CCtrl 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000013 1 1 00000007 0000000b +CCtrl 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +AddCurve +Curve 00000014 1 1 00000008 0000000b +CCtrl 0 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000015 1 1 0000000b 0000000c +CCtrl 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000016 1 1 00000007 0000000c +CCtrl 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +AddCurve +Curve 00000017 1 1 00000008 0000000c +CCtrl 0 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000018 1 1 0000000c 00000009 +CCtrl 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddCurve diff --git a/test/group/translate_asy/normal_v22.slvs b/test/group/translate_asy/normal_v22.slvs new file mode 100644 index 0000000..2f013bc --- /dev/null +++ b/test/group/translate_asy/normal_v22.slvs @@ -0,0 +1,2088 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000003 +Group.type=5100 +Group.order=2 +Group.name=extrude +Group.opA.v=00000002 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.meshCombine=2 +Group.predef.entityB.v=80020000 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00040000 1002 + 2 00040001 1002 + 3 00040002 1002 + 4 00040000 1001 + 5 00040001 1001 + 6 00040002 1001 + 7 00040000 1004 + 8 00040001 1003 + 9 00040002 1003 + 10 00050000 1002 + 11 00050001 1002 + 12 00050002 1002 + 13 00050000 1001 + 14 00050001 1001 + 15 00050002 1001 + 16 00050000 1004 + 17 00050001 1003 + 18 00050002 1003 + 19 00060000 1002 + 20 00060001 1002 + 21 00060002 1002 + 22 00060000 1001 + 23 00060001 1001 + 24 00060002 1001 + 25 00060000 1004 + 26 00060001 1003 + 27 00060002 1003 + 28 00070000 1002 + 29 00070001 1002 + 30 00070002 1002 + 31 00070000 1001 + 32 00070001 1001 + 33 00070002 1001 + 34 00070000 1004 + 35 00070001 1003 + 36 00070002 1003 + 37 80020000 1002 + 38 80020000 1001 + 39 80020001 1002 + 40 80020002 1002 + 41 80020001 1001 + 42 80020002 1001 + 43 80020002 1003 + 44 00000000 1001 + 45 00000000 1002 +} +AddGroup + +Group.h.v=00000004 +Group.type=5201 +Group.order=3 +Group.name=translate +Group.opA.v=00000003 +Group.valA=2.00000000000000000000 +Group.color=00646464 +Group.subtype=7000 +Group.skipFirst=0 +Group.meshCombine=2 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 80030001 0 + 2 80030002 0 + 3 80030003 0 + 4 80030004 0 + 5 80030005 0 + 6 80030006 0 + 7 80030007 0 + 8 80030008 0 + 9 80030009 0 + 10 8003000a 0 + 11 8003000b 0 + 12 8003000c 0 + 13 8003000d 0 + 14 8003000e 0 + 15 8003000f 0 + 16 80030010 0 + 17 80030011 0 + 18 80030012 0 + 19 80030013 0 + 20 80030014 0 + 21 80030015 0 + 22 80030016 0 + 23 80030017 0 + 24 80030018 0 + 25 80030019 0 + 26 8003001a 0 + 27 8003001b 0 + 28 8003001c 0 + 29 8003001d 0 + 30 8003001e 0 + 31 8003001f 0 + 32 80030020 0 + 33 80030021 0 + 34 80030022 0 + 35 80030023 0 + 36 80030024 0 + 37 80030027 0 + 38 80030028 0 + 39 80030029 0 + 40 8003002a 0 + 41 8003002b 0 + 42 8003002c 0 + 43 8003002d 0 + 44 80030001 1 + 45 80030002 1 + 46 80030003 1 + 47 80030004 1 + 48 80030005 1 + 49 80030006 1 + 50 80030007 1 + 51 80030008 1 + 52 80030009 1 + 53 8003000a 1 + 54 8003000b 1 + 55 8003000c 1 + 56 8003000d 1 + 57 8003000e 1 + 58 8003000f 1 + 59 80030010 1 + 60 80030011 1 + 61 80030012 1 + 62 80030013 1 + 63 80030014 1 + 64 80030015 1 + 65 80030016 1 + 66 80030017 1 + 67 80030018 1 + 68 80030019 1 + 69 8003001a 1 + 70 8003001b 1 + 71 8003001c 1 + 72 8003001d 1 + 73 8003001e 1 + 74 8003001f 1 + 75 80030020 1 + 76 80030021 1 + 77 80030022 1 + 78 80030023 1 + 79 80030024 1 + 80 80030027 1 + 81 80030028 1 + 82 80030029 1 + 83 8003002a 1 + 84 8003002b 1 + 85 8003002c 1 + 86 8003002d 1 + 87 80030001 1000 + 88 80030002 1000 + 89 80030003 1000 + 90 80030004 1000 + 91 80030005 1000 + 92 80030006 1000 + 93 80030007 1000 + 94 80030008 1000 + 95 80030009 1000 + 96 8003000a 1000 + 97 8003000b 1000 + 98 8003000c 1000 + 99 8003000d 1000 + 100 8003000e 1000 + 101 8003000f 1000 + 102 80030010 1000 + 103 80030011 1000 + 104 80030012 1000 + 105 80030013 1000 + 106 80030014 1000 + 107 80030015 1000 + 108 80030016 1000 + 109 80030017 1000 + 110 80030018 1000 + 111 80030019 1000 + 112 8003001a 1000 + 113 8003001b 1000 + 114 8003001c 1000 + 115 8003001d 1000 + 116 8003001e 1000 + 117 8003001f 1000 + 118 80030020 1000 + 119 80030021 1000 + 120 80030022 1000 + 121 80030023 1000 + 122 80030024 1000 + 123 80030027 1000 + 124 80030028 1000 + 125 80030029 1000 + 126 8003002a 1000 + 127 8003002b 1000 + 128 8003002c 1000 + 129 8003002d 1000 +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00050010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050011 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00050013 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00050014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00060013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00060014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00070010 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00070011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00070013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00070014 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030000 +AddParam + +Param.h.v.=80030001 +AddParam + +Param.h.v.=80030002 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80040000 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80040001 +Param.val=2.50000000000000000000 +AddParam + +Param.h.v.=80040002 +Param.val=-2.50000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000005 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000006 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Request.h.v=00000007 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00050001 +Entity.point[1].v=00050002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00050002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00060001 +Entity.point[1].v=00060002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00060002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00070001 +Entity.point[1].v=00070002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00070002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030001 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.point[1].v=80030003 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030004 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030005 +Entity.point[1].v=80030006 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030007 +Entity.type=5001 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actNormal.vx=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030005 +Entity.point[1].v=80030002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030006 +Entity.point[1].v=80030003 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000b +Entity.point[1].v=8003000c +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000b +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000e +Entity.point[1].v=8003000f +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030010 +Entity.type=5001 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actNormal.vy=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000e +Entity.point[1].v=8003000b +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003000f +Entity.point[1].v=8003000c +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030013 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030014 +Entity.point[1].v=80030015 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030014 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030015 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030016 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030017 +Entity.point[1].v=80030018 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030017 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030018 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030019 +Entity.type=5001 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030017 +Entity.point[1].v=80030014 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001b +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030018 +Entity.point[1].v=80030015 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001c +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003001d +Entity.point[1].v=8003001e +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001d +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001e +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003001f +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030020 +Entity.point[1].v=80030021 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030020 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030021 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030022 +Entity.type=5001 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actNormal.vy=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030023 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030020 +Entity.point[1].v=8003001d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030024 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80030021 +Entity.point[1].v=8003001e +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030027 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80030028 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030028 +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030029 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8003002a +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003002a +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003002b +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8003002a +Entity.point[1].v=80030028 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003002c +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=8003002a +Entity.actPoint.z=10.00000000000000000000 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003002d +Entity.type=5000 +Entity.construction=0 +Entity.point[0].v=80030028 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040001 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040002 +Entity.point[1].v=80040003 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040002 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040003 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040004 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040005 +Entity.point[1].v=80040006 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040005 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040006 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040007 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actNormal.vx=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040008 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040005 +Entity.point[1].v=80040002 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040009 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040006 +Entity.point[1].v=80040003 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004000b +Entity.point[1].v=8004000c +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000b +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000c +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000d +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004000e +Entity.point[1].v=8004000f +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000e +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004000f +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040010 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actNormal.vy=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040011 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004000e +Entity.point[1].v=8004000b +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040012 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004000f +Entity.point[1].v=8004000c +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040013 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040014 +Entity.point[1].v=80040015 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040014 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040015 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040016 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040017 +Entity.point[1].v=80040018 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040017 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040018 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040019 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040017 +Entity.point[1].v=80040014 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001b +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040018 +Entity.point[1].v=80040015 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001c +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004001d +Entity.point[1].v=8004001e +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001d +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001e +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004001f +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040020 +Entity.point[1].v=80040021 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040020 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040021 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040022 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actNormal.vy=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040023 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040020 +Entity.point[1].v=8004001d +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040024 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040021 +Entity.point[1].v=8004001e +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040025 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80040026 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040026 +Entity.type=2010 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040027 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80040028 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040028 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.z=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040029 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040028 +Entity.point[1].v=80040026 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004002a +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.z=10.00000000000000000000 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004002b +Entity.type=5003 +Entity.construction=0 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040057 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040058 +Entity.point[1].v=80040059 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040058 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040059 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004005b +Entity.point[1].v=8004005c +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005b +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005c +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005d +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actNormal.vx=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005e +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004005b +Entity.point[1].v=80040058 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004005f +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004005c +Entity.point[1].v=80040059 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040060 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040061 +Entity.point[1].v=80040062 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040061 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040062 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040063 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040064 +Entity.point[1].v=80040065 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040064 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040065 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040066 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actNormal.vy=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040067 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040064 +Entity.point[1].v=80040061 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040068 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040065 +Entity.point[1].v=80040062 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040069 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004006a +Entity.point[1].v=8004006b +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006a +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006b +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006c +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004006d +Entity.point[1].v=8004006e +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006d +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006e +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004006f +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actNormal.vx=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040070 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004006d +Entity.point[1].v=8004006a +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040071 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004006e +Entity.point[1].v=8004006b +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040072 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040073 +Entity.point[1].v=80040074 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040073 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040074 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040075 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040076 +Entity.point[1].v=80040077 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040076 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040077 +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040078 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actNormal.vy=-1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040079 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040076 +Entity.point[1].v=80040073 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007a +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=80040077 +Entity.point[1].v=80040074 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007b +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8004007c +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007c +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007d +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=8004007e +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007e +Entity.type=2010 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8004007f +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=8004007e +Entity.point[1].v=8004007c +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040080 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=5.00000000000000000000 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80040081 +Entity.type=5003 +Entity.construction=0 +Entity.actPoint.x=10.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actPoint.z=-5.00000000000000000000 +Entity.actNormal.vz=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00040001 +Constraint.ptB.v=00050002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000002 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00050001 +Constraint.ptB.v=00060002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000003 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00060001 +Constraint.ptB.v=00070002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000004 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.ptA.v=00070001 +Constraint.ptB.v=00040002 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000005 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00040000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000006 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00050000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000007 +Constraint.type=81 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00060000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Constraint.h.v=00000008 +Constraint.type=80 +Constraint.group.v=00000002 +Constraint.workplane.v=80020000 +Constraint.entityA.v=00070000 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + +Surface 00000001 00646464 8004002a 1 1 +SCtrl 0 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000010000000827 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000000000 -5.00000000010000000827 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000010000000827 -5.00000000010000000827 10.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000001 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +TrimBy 00000004 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 00000007 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 0000000a 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +AddSurface +Surface 00000002 00646464 8004002b 1 1 +SCtrl 0 0 10.00000000010000000827 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000010000000827 -5.00000000010000000827 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 -5.00000000000000000000 -5.00000000010000000827 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000002 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +TrimBy 00000005 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +TrimBy 00000008 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000b 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000003 00646464 80040010 1 1 +SCtrl 0 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000001 1 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +TrimBy 00000002 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +TrimBy 00000003 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +TrimBy 0000000c 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000004 00646464 80040019 1 1 +SCtrl 0 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000004 1 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +TrimBy 00000005 0 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000006 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 00000003 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000005 00646464 80040022 1 1 +SCtrl 0 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000007 1 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 00000008 0 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +TrimBy 00000009 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 00000006 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000006 00646464 80040007 1 1 +SCtrl 0 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000a 1 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +TrimBy 0000000b 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +TrimBy 0000000c 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +TrimBy 00000009 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddSurface +Surface 00000007 00646464 80040080 1 1 +SCtrl 0 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 20.00000000010000178463 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000000000000000 -0.00000000010000000827 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 20.00000000010000178463 -0.00000000010000000827 5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000d 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000010 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 00000013 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 00000016 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +AddSurface +Surface 00000008 00646464 80040081 1 1 +SCtrl 0 0 20.00000000010000178463 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 20.00000000010000178463 -0.00000000010000000827 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 5.00000000000000000000 -0.00000000010000000827 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000e 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +TrimBy 00000011 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +TrimBy 00000014 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +TrimBy 00000017 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddSurface +Surface 00000009 00646464 80040066 1 1 +SCtrl 0 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 0000000d 1 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 0000000e 0 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +TrimBy 0000000f 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000018 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddSurface +Surface 0000000a 00646464 8004006f 1 1 +SCtrl 0 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000010 1 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000011 0 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +TrimBy 00000012 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 0000000f 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddSurface +Surface 0000000b 00646464 80040078 1 1 +SCtrl 0 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000013 1 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 00000014 0 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +TrimBy 00000015 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 00000012 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddSurface +Surface 0000000c 00646464 8004005d 1 1 +SCtrl 0 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 0 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +SCtrl 1 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +TrimBy 00000016 1 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +TrimBy 00000017 0 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +TrimBy 00000018 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +TrimBy 00000015 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddSurface +Curve 00000001 1 1 00000001 00000003 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +AddCurve +Curve 00000002 1 1 00000002 00000003 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000003 1 1 00000003 00000004 +CCtrl 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000004 1 1 00000001 00000004 +CCtrl 0 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +AddCurve +Curve 00000005 1 1 00000002 00000004 +CCtrl 0 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000006 1 1 00000004 00000005 +CCtrl 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000007 1 1 00000001 00000005 +CCtrl 0 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +AddCurve +Curve 00000008 1 1 00000002 00000005 +CCtrl 0 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 -5.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 00000009 1 1 00000005 00000006 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 0000000a 1 1 00000001 00000006 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 10.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +AddCurve +Curve 0000000b 1 1 00000002 00000006 +CCtrl 0 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 5.00000000000000000000 0.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 0000000c 1 1 00000006 00000003 +CCtrl 0 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 10.00000000000000000000 +CurvePt 1 10.00000000000000000000 -5.00000000000000000000 0.00000000000000000000 +AddCurve +Curve 0000000d 1 1 00000007 00000009 +CCtrl 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +AddCurve +Curve 0000000e 1 1 00000008 00000009 +CCtrl 0 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 0000000f 1 1 00000009 0000000a +CCtrl 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000010 1 1 00000007 0000000a +CCtrl 0 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +AddCurve +Curve 00000011 1 1 00000008 0000000a +CCtrl 0 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000012 1 1 0000000a 0000000b +CCtrl 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000013 1 1 00000007 0000000b +CCtrl 0 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +AddCurve +Curve 00000014 1 1 00000008 0000000b +CCtrl 0 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 5.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000015 1 1 0000000b 0000000c +CCtrl 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000016 1 1 00000007 0000000c +CCtrl 0 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 5.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +AddCurve +Curve 00000017 1 1 00000008 0000000c +CCtrl 0 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 10.00000000000000000000 -5.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddCurve +Curve 00000018 1 1 0000000c 00000009 +CCtrl 0 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 Weight 1.00000000000000000000 +CCtrl 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 Weight 1.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 5.00000000000000000000 +CurvePt 1 20.00000000000000000000 0.00000000000000000000 -5.00000000000000000000 +AddCurve diff --git a/test/group/translate_asy/test.cpp b/test/group/translate_asy/test.cpp new file mode 100644 index 0000000..957dcce --- /dev/null +++ b/test/group/translate_asy/test.cpp @@ -0,0 +1,29 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER_ISO("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_inters) { + CHECK_LOAD("normal.slvs"); + + Group *g = SK.GetGroup(SS.GW.activeGroup); + g->GenerateDisplayItems(); + SMesh *m = &g->displayMesh; + + SEdgeList el = {}; + bool inters, leaks; + SKdNode::From(m)->MakeCertainEdgesInto(&el, + EdgeKind::SELF_INTER, /*coplanarIsInter=*/false, &inters, &leaks); + el.Clear(); + + // The assembly is supposed to interfere. + CHECK_TRUE(inters); +} diff --git a/test/group/translate_nd/normal.png b/test/group/translate_nd/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..4f5ee2c74ccb18e3990e448ba91446e4f0d2cfcc GIT binary patch literal 4983 zcmd5=dsx!<9{z%v<|S-d&AiaoN-HXxndH39Y|dk@i>8zYIF~0QGt0ZC*hN>CJnb~G zC^c)=6ctOfloT`Nlz6F*Xd#+0@j_~ah@isxfv!opX3u%{Jm-1fA1=S&=lglz&-=cg z->Gfix$5gI)&T(MyM61j0{}Qi`PQBdeNqva-Uh&Y2RE0my`nDl^E`GKKd4CVq#9FS z%>B)D=stGVakFSG)6ndOFZJpU)tu@vwd{kX5Zwlui!TND%mp~NvovDnP$4SA{1`JX zY^OJQMdd@(wJY?JAOP-Vwx2 zPQ!ri-r3XihZw)bQ>;yFH{4^R-poyeU{8B(KwP%&_#fSFLKOPzggMWk$C!fb?nZS~ znl;l}3^l;QhkDbYHlCi-HxJxhQFc&U4dNuuvz!HxzI&*A)!q^pvT?ZoHd7>H8W0)Tt@u6XT=w zJ+tP&KL8MI!NT7LP8J^B$h80tb6a>%zkG`)!)xwFt(@G-J(t4`0kT%ytcxpl8Nr+Q zQgUt$a#Y8pv~9d2%;R|De44BAFbsEzE~*w=4Q3 z$&v(lFh}S4`3!F@`AEP2a-pp7;Uil8l%D-DsJvH(`pmoq|MN@lcR|`r$Ohng z;1v3OSb|<=#{f$kAw^tOm|s!Qfc{AmY9e}p7VKTYs)~cATDI=2l3`XHzlo7s=8S$U z5arO3%~#Rwha9~So!2grqC*|JA_nGOr)3i=}&SQ1W>|4Oq*#;ae)t#?Eo`f&x}Oa3R)74NNNH4AS`HqJ$-8lkd`^SIo;&mgUnowQYx4$K~W^iSIX{t8=- zgEO$Yuz;h{yXDeC5alt^Bx=`G6HW*+4?k zya}Rs>J)I_AB$bTg>ETFM#h@Nny{p+2has(j-jREnzjEl)BCCqPEhL5O`ZmGhaYYy ziFhL?7`t)F>M*ufY(f*878uFqoX|z+} zm>$t6kzG*iIU-dk8iPH``5iKmJoG?Cg5!SVf}{k+=<^m1Sp+AZQB9>6{m_`Ey-3I%L<}m%$}>klEio;#2@u^8 zxOU}7zzJK)lJw%?5mIe+c(wFKCqxx&85v>;>2yrB$G`+vj)hBvA-xwSxN;b=u!$6j z;(JM3_{Q6=?W42x7QT$$Z8q|n-k7k^=B#>^+q10DDuD`xDQ%6e_VV_A#pmXG+ zp+x8sx?dp^#)fHLQR6;c`&uEU+{*~0%0(|CVeWya43>QB2g~2tfS2k!aXWLH2`bE zVkl*#u*`^>ZM_{3J#W1Ygn!ZL-(zjlS#j51u>rCDOu&PKnre9|JaK37avgAJ|Frhl zQ`Oo8TTh!eK9s0=7#vn*k29gc9xntC1RlNRFhXx`X*P0w zpJ13&>cNmm2P`t`8}dd4|LzwJ zEQU#}!!z_9t-N&Rv8DQwO}ItWa$JIYL~e-R|7~sf^*ne-J2owN&2w@n`3CWT9eqA+F#V{X+>7r+tuF}<7zZI&1b;q|Bq}qxuj$n3+w8X5x|HwMJ0z{!wH`D z0N{I7Qdvx2eiIP?C5uv?&ivgF;AG=nTBmAP+0v z)N7H}J0RVfP;M)<=xGxx2JSwrj(T3zI@IWMd}J7hXnPC|QFNo!vM3Y_PcccXnPakB z_DYhQ1R=Y2jeeO{-g)Nd-A;HqXDmw;A;;FIs3zw?^u^gwgjRtzt%#}O=J=y18SVm2Na(xKbIXcB77|XVWe38XGNG;euA!?HMH3vobHj`o z#5ljCdTr%ZElP67yvN*Y$fOeGlzzVy{U}4#f%G_&|cezUTv&3!({V2Ee_t)ATi=fHU o-FwPlX8X +#include + +#include "harness.h" + +#if defined(WIN32) +# include +#else +# include +#endif + +namespace SolveSpace { +namespace Platform { + // These are defined in headless.cpp, and aren't exposed in solvespace.h. + extern std::vector fontFiles; +} +} + + +#ifdef TEST_BUILD_ON_WINDOWS +static const char *VALID_BUILD_PATH_SEPS = "/\\"; +static char BUILD_PATH_SEP = '\\'; +#else +static const char *VALID_BUILD_PATH_SEPS = "/"; +static char BUILD_PATH_SEP = '/'; +#endif + +static std::string BuildRoot() { + static std::string rootDir; + if(!rootDir.empty()) return rootDir; + + rootDir = __FILE__; + rootDir.erase(rootDir.find_last_of(VALID_BUILD_PATH_SEPS) + 1); + return rootDir; +} + +static Platform::Path HostRoot() { + static Platform::Path rootDir; + if(!rootDir.IsEmpty()) return rootDir; + + // No especially good way to do this, so let's assume somewhere up from + // the current directory there's our repository, with CMakeLists.txt, and + // pivot from there. + rootDir = Platform::Path::CurrentDirectory(); + + // We're never more than four levels deep. + for(size_t i = 0; i < 4; i++) { + FILE *f = OpenFile(rootDir.Join("CMakeLists.txt"), "r"); + if(f) { + fclose(f); + rootDir = rootDir.Join("test"); + return rootDir; + } + rootDir = rootDir.Parent(); + } + + ssassert(false, "Couldn't locate repository root"); +} + +enum class Color { + Red, + Green, + DarkGreen +}; + +static std::string Colorize(Color color, std::string input) { +#if !defined(WIN32) + if(isatty(fileno(stdout))) { + switch(color) { + case Color::Red: + return "\e[1;31m" + input + "\e[0m"; + case Color::Green: + return "\e[1;32m" + input + "\e[0m"; + case Color::DarkGreen: + return "\e[36m" + input + "\e[0m"; + } + } +#endif + return input; +} + +// Normalizes a savefile. Different platforms have slightly different floating-point +// behavior, so if we want to compare savefiles byte-by-byte, we need to do something +// to get rid of irrelevant differences in LSB. +static std::string PrepareSavefile(std::string data) { + // Round everything to 2**30 ~ 1e9 + const double precision = pow(2, 30); + + size_t lineBegin = 0; + while(lineBegin < data.length()) { + size_t nextLineBegin = data.find('\n', lineBegin); + if(nextLineBegin == std::string::npos) { + nextLineBegin = data.length(); + } else { + nextLineBegin++; + } + + size_t eqPos = data.find('=', lineBegin); + if(eqPos < nextLineBegin) { + std::string key = data.substr(lineBegin, eqPos - lineBegin), + value = data.substr(eqPos + 1, nextLineBegin - eqPos - 2); + + for(int i = 0; SolveSpaceUI::SAVED[i].type != 0; i++) { + if(SolveSpaceUI::SAVED[i].fmt != 'f') continue; + if(SolveSpaceUI::SAVED[i].desc != key) continue; + double f = strtod(value.c_str(), NULL); + f = round(f * precision) / precision; + std::string newValue = ssprintf("%.20f", f); + ssassert(value.size() == newValue.size(), "Expected no change in value length"); + std::copy(newValue.begin(), newValue.end(), + data.begin() + eqPos + 1); + } + + if(key == "Group.impFile") { + data.erase(lineBegin, nextLineBegin - lineBegin); + nextLineBegin = lineBegin; + } + } + + size_t spPos = data.find(' ', lineBegin); + if(spPos < nextLineBegin) { + std::string cmd = data.substr(lineBegin, spPos - lineBegin); + if(!cmd.empty()) { + if(cmd == "Surface" || cmd == "SCtrl" || cmd == "TrimBy" || + cmd == "Curve" || cmd == "CCtrl" || cmd == "CurvePt") { + data.erase(lineBegin, nextLineBegin - lineBegin); + nextLineBegin = lineBegin; + } + } + } + + lineBegin = nextLineBegin; + } + return data; +} + +bool Test::Helper::RecordCheck(bool success) { + checkCount++; + if(!success) failCount++; + return success; +} + +void Test::Helper::PrintFailure(const char *file, int line, std::string msg) { + std::string shortFile = file; + shortFile.erase(0, BuildRoot().size()); + fprintf(stderr, "test%c%s:%d: FAILED: %s\n", + BUILD_PATH_SEP, shortFile.c_str(), line, msg.c_str()); +} + +Platform::Path Test::Helper::GetAssetPath(std::string testFile, std::string assetName, + std::string mangle) { + if(!mangle.empty()) { + assetName.insert(assetName.rfind('.'), "." + mangle); + } + testFile.erase(0, BuildRoot().size()); + testFile.erase(testFile.find_last_of(VALID_BUILD_PATH_SEPS) + 1); + return HostRoot().Join(Platform::Path::FromPortable(testFile + assetName)); +} + +bool Test::Helper::CheckBool(const char *file, int line, const char *expr, bool value, + bool reference) { + if(!RecordCheck(value == reference)) { + std::string msg = ssprintf("(%s) = %s ≠ %s", expr, + value ? "true" : "false", + reference ? "true" : "false"); + PrintFailure(file, line, msg); + return false; + } else { + return true; + } +} + +bool Test::Helper::CheckEqualString(const char *file, int line, const char *valueExpr, + const std::string &value, const std::string &reference) { + if(!RecordCheck(value == reference)) { + std::string msg = ssprintf("(%s) = \"%s\" ≠ \"%s\"", valueExpr, + value.c_str(), reference.c_str()); + PrintFailure(file, line, msg); + return false; + } else { + return true; + } +} + +bool Test::Helper::CheckEqualEpsilon(const char *file, int line, const char *valueExpr, + double value, double reference) { + bool result = fabs(value - reference) < LENGTH_EPS; + if(!RecordCheck(result)) { + std::string msg = ssprintf("(%s) = %.4g ≉ %.4g", valueExpr, + value, reference); + PrintFailure(file, line, msg); + return false; + } else { + return true; + } +} + +bool Test::Helper::CheckLoad(const char *file, int line, const char *fixture) { + Platform::Path fixturePath = GetAssetPath(file, fixture); + + FILE *f = OpenFile(fixturePath, "rb"); + bool fixtureExists = (f != NULL); + if(f) fclose(f); + + bool result = fixtureExists && SS.LoadFromFile(fixturePath); + if(!RecordCheck(result)) { + PrintFailure(file, line, + ssprintf("loading file '%s'", fixturePath.raw.c_str())); + return false; + } else { + SS.AfterNewFile(); + SS.GW.offset = {}; + SS.GW.scale = 10.0; + return true; + } +} + +bool Test::Helper::CheckSave(const char *file, int line, const char *reference) { + Platform::Path refPath = GetAssetPath(file, reference), + outPath = GetAssetPath(file, reference, "out"); + if(!RecordCheck(SS.SaveToFile(outPath))) { + PrintFailure(file, line, + ssprintf("saving file '%s'", refPath.raw.c_str())); + return false; + } else { + std::string refData, outData; + ReadFile(refPath, &refData); + ReadFile(outPath, &outData); + if(!RecordCheck(PrepareSavefile(refData) == PrepareSavefile(outData))) { + PrintFailure(file, line, "savefile doesn't match reference"); + return false; + } + + RemoveFile(outPath); + return true; + } +} + +bool Test::Helper::CheckRender(const char *file, int line, const char *reference) { + // First, render to a framebuffer. + Camera camera = {}; + camera.pixelRatio = 1; + camera.gridFit = true; + camera.width = 600; + camera.height = 600; + camera.projUp = SS.GW.projUp; + camera.projRight = SS.GW.projRight; + camera.scale = SS.GW.scale; + + CairoPixmapRenderer pixmapCanvas; + pixmapCanvas.SetLighting(SS.GW.GetLighting()); + pixmapCanvas.SetCamera(camera); + pixmapCanvas.Init(); + + pixmapCanvas.StartFrame(); + SS.GW.Draw(&pixmapCanvas); + pixmapCanvas.FlushFrame(); + pixmapCanvas.FinishFrame(); + std::shared_ptr frame = pixmapCanvas.ReadFrame(); + + pixmapCanvas.Clear(); + + // Now, diff framebuffer against reference render. + Platform::Path refPath = GetAssetPath(file, reference), + outPath = GetAssetPath(file, reference, "out"), + diffPath = GetAssetPath(file, reference, "diff"); + + std::shared_ptr refPixmap = Pixmap::ReadPng(refPath, /*flip=*/true); + if(!RecordCheck(refPixmap && refPixmap->Equals(*frame))) { + frame->WritePng(outPath, /*flip=*/true); + + if(!refPixmap) { + PrintFailure(file, line, "reference render not present"); + return false; + } + + ssassert(refPixmap->format == frame->format, "Expected buffer formats to match"); + if(refPixmap->width != frame->width || + refPixmap->height != frame->height) { + PrintFailure(file, line, "render doesn't match reference; dimensions differ"); + } else { + std::shared_ptr diffPixmap = + Pixmap::Create(refPixmap->format, refPixmap->width, refPixmap->height); + + int diffPixelCount = 0; + for(size_t j = 0; j < refPixmap->height; j++) { + for(size_t i = 0; i < refPixmap->width; i++) { + if(!refPixmap->GetPixel(i, j).Equals(frame->GetPixel(i, j))) { + diffPixelCount++; + diffPixmap->SetPixel(i, j, RgbaColor::From(255, 0, 0, 255)); + } + } + } + + diffPixmap->WritePng(diffPath, /*flip=*/true); + std::string message = + ssprintf("render doesn't match reference; %d (%.2f%%) pixels differ", + diffPixelCount, + 100.0 * diffPixelCount / (refPixmap->width * refPixmap->height)); + PrintFailure(file, line, message); + } + return false; + } else { + RemoveFile(outPath); + RemoveFile(diffPath); + return true; + } +} + +bool Test::Helper::CheckRenderXY(const char *file, int line, const char *fixture) { + SS.GW.projRight = Vector::From(1, 0, 0); + SS.GW.projUp = Vector::From(0, 1, 0); + return CheckRender(file, line, fixture); +} + +bool Test::Helper::CheckRenderIso(const char *file, int line, const char *fixture) { + SS.GW.projRight = Vector::From(0.707, 0.000, -0.707); + SS.GW.projUp = Vector::From(-0.408, 0.816, -0.408); + return CheckRender(file, line, fixture); +} + +// Avoid global constructors; using a global static vector instead of a local one +// breaks MinGW for some obscure reason. +static std::vector *testCasesPtr; +int Test::Case::Register(Test::Case testCase) { + static std::vector testCases; + testCases.push_back(testCase); + testCasesPtr = &testCases; + return 0; +} + +int main(int argc, char **argv) { + std::vector args = Platform::InitCli(argc, argv); + + std::regex filter(".*"); + if(args.size() == 1) { + } else if(args.size() == 2) { + filter = args[1]; + } else { + fprintf(stderr, "Usage: %s [test filter regex]\n", args[0].c_str()); + return 1; + } + + Platform::fontFiles.push_back(HostRoot().Join("Gentium-R.ttf")); + + // Wreck order dependencies between tests! + std::random_shuffle(testCasesPtr->begin(), testCasesPtr->end()); + + auto testStartTime = std::chrono::steady_clock::now(); + size_t ranTally = 0, skippedTally = 0, checkTally = 0, failTally = 0; + for(Test::Case &testCase : *testCasesPtr) { + std::string testCaseName = testCase.fileName; + testCaseName.erase(0, BuildRoot().size()); + testCaseName.erase(testCaseName.find_last_of(VALID_BUILD_PATH_SEPS)); + testCaseName += BUILD_PATH_SEP + testCase.caseName; + + std::smatch filterMatch; + if(!std::regex_search(testCaseName, filterMatch, filter)) { + skippedTally += 1; + continue; + } + + SS.Init(); + SS.showToolbar = false; + SS.checkClosedContour = false; + + Test::Helper helper = {}; + testCase.fn(&helper); + + SK.Clear(); + SS.Clear(); + + ranTally += 1; + checkTally += helper.checkCount; + failTally += helper.failCount; + if(helper.checkCount == 0) { + fprintf(stderr, " %s test %s (empty)\n", + Colorize(Color::Red, "??").c_str(), + Colorize(Color::DarkGreen, testCaseName).c_str()); + } else if(helper.failCount > 0) { + fprintf(stderr, " %s test %s\n", + Colorize(Color::Red, "NG").c_str(), + Colorize(Color::DarkGreen, testCaseName).c_str()); + } else { + fprintf(stderr, " %s test %s\n", + Colorize(Color::Green, "OK").c_str(), + Colorize(Color::DarkGreen, testCaseName).c_str()); + } + } + + auto testEndTime = std::chrono::steady_clock::now(); + std::chrono::duration testTime = testEndTime - testStartTime; + + if(failTally > 0) { + fprintf(stderr, "Failure! %u checks failed\n", + (unsigned)failTally); + } else { + fprintf(stderr, "Success! %u test cases (%u skipped), %u checks, %.3fs\n", + (unsigned)ranTally, (unsigned)skippedTally, + (unsigned)checkTally, testTime.count()); + } + + // At last, try to reset all caches we or our dependencies have, to make SNR + // of memory checking tools like valgrind higher. + cairo_debug_reset_static_data(); + + return (failTally > 0); +} diff --git a/test/harness.h b/test/harness.h new file mode 100644 index 0000000..fa95900 --- /dev/null +++ b/test/harness.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// Our harness for running test cases, and reusable checks. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" + +// Hack... we should rename the ones in ui.h instead. +#undef CHECK_TRUE +#undef CHECK_FALSE + +namespace SolveSpace { +namespace Test { + +class Helper { +public: + size_t checkCount; + size_t failCount; + + bool RecordCheck(bool success); + void PrintFailure(const char *file, int line, std::string msg); + Platform::Path GetAssetPath(std::string testFile, std::string assetName, + std::string mangle = ""); + + bool CheckBool(const char *file, int line, const char *expr, + bool value, bool reference); + bool CheckEqualString(const char *file, int line, const char *valueExpr, + const std::string &value, const std::string &reference); + bool CheckEqualEpsilon(const char *file, int line, const char *valueExpr, + double value, double reference); + bool CheckLoad(const char *file, int line, const char *fixture); + bool CheckSave(const char *file, int line, const char *reference); + bool CheckRender(const char *file, int line, const char *fixture); + bool CheckRenderXY(const char *file, int line, const char *fixture); + bool CheckRenderIso(const char *file, int line, const char *fixture); +}; + +class Case { +public: + std::string fileName; + std::string caseName; + std::function fn; + + static int Register(Case testCase); +}; + +} +} + +using namespace SolveSpace; + +#define TEST_CASE(name) \ + static void Test_##name(Test::Helper *); \ + static Test::Case TestCase_##name = { __FILE__, #name, Test_##name }; \ + static int TestReg_##name = Test::Case::Register(TestCase_##name); \ + static void Test_##name(Test::Helper *helper) // { ... } + +#define CHECK_TRUE(cond) \ + do { if(!helper->CheckBool(__FILE__, __LINE__, #cond, cond, true)) return; } while(0) +#define CHECK_FALSE(cond) \ + do { if(!helper->CheckBool(__FILE__, __LINE__, #cond, cond, false)) return; } while(0) +#define CHECK_EQ_STR(value, reference) \ + do { if(!helper->CheckEqualString(__FILE__, __LINE__, \ + #value, value, reference)) return; } while(0) +#define CHECK_EQ_EPS(value, reference) \ + do { if(!helper->CheckEqualEpsilon(__FILE__, __LINE__, \ + #value, value, reference)) return; } while(0) +#define CHECK_LOAD(fixture) \ + do { if(!helper->CheckLoad(__FILE__, __LINE__, fixture)) return; } while(0) +#define CHECK_SAVE(fixture) \ + do { if(!helper->CheckSave(__FILE__, __LINE__, fixture)) return; } while(0) +#define CHECK_RENDER(reference) \ + do { if(!helper->CheckRenderXY(__FILE__, __LINE__, reference)) return; } while(0) +#define CHECK_RENDER_ISO(reference) \ + do { if(!helper->CheckRenderIso(__FILE__, __LINE__, reference)) return; } while(0) diff --git a/test/request/arc_of_circle/normal.png b/test/request/arc_of_circle/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..2839007cc95fc697f1a43d21aae85094b3dc6524 GIT binary patch literal 4560 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 U!C812`=tiBIC<>gY=`6i1zXKwMF0Q* literal 0 HcmV?d00001 diff --git a/test/request/arc_of_circle/normal.slvs b/test/request/arc_of_circle/normal.slvs new file mode 100644 index 0000000..aaa6b8f --- /dev/null +++ b/test/request/arc_of_circle/normal.slvs @@ -0,0 +1,306 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040016 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040017 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=500 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=14000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/arc_of_circle/normal_v20.slvs b/test/request/arc_of_circle/normal_v20.slvs new file mode 100644 index 0000000..78d983b --- /dev/null +++ b/test/request/arc_of_circle/normal_v20.slvs @@ -0,0 +1,306 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040016 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040017 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=500 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=14000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/arc_of_circle/normal_v22.slvs b/test/request/arc_of_circle/normal_v22.slvs new file mode 100644 index 0000000..6f92e0e --- /dev/null +++ b/test/request/arc_of_circle/normal_v22.slvs @@ -0,0 +1,306 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040016 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040017 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=500 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=14000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/arc_of_circle/test.cpp b/test/request/arc_of_circle/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/request/arc_of_circle/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/request/circle/free_in_3d.png b/test/request/circle/free_in_3d.png new file mode 100644 index 0000000000000000000000000000000000000000..31e2499f9dd2402663bfd7a77611b323bf906d42 GIT binary patch literal 4796 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 literal 0 HcmV?d00001 diff --git a/test/request/circle/free_in_3d.slvs b/test/request/circle/free_in_3d.slvs new file mode 100644 index 0000000..41287aa --- /dev/null +++ b/test/request/circle/free_in_3d.slvs @@ -0,0 +1,294 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00040021 +AddParam + +Param.h.v.=00040022 +AddParam + +Param.h.v.=00040023 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/circle/free_in_3d_dof.png b/test/request/circle/free_in_3d_dof.png new file mode 100644 index 0000000000000000000000000000000000000000..2b5dff0e5ea948effb9fce02f99a9f59e235c707 GIT binary patch literal 7195 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 LW`_Ct* literal 0 HcmV?d00001 diff --git a/test/request/circle/free_in_3d_v20.slvs b/test/request/circle/free_in_3d_v20.slvs new file mode 100644 index 0000000..6966f95 --- /dev/null +++ b/test/request/circle/free_in_3d_v20.slvs @@ -0,0 +1,292 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00040021 +AddParam + +Param.h.v.=00040022 +AddParam + +Param.h.v.=00040023 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/circle/free_in_3d_v22.slvs b/test/request/circle/free_in_3d_v22.slvs new file mode 100644 index 0000000..ff35698 --- /dev/null +++ b/test/request/circle/free_in_3d_v22.slvs @@ -0,0 +1,294 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00040021 +AddParam + +Param.h.v.=00040022 +AddParam + +Param.h.v.=00040023 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/circle/normal.png b/test/request/circle/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..31e2499f9dd2402663bfd7a77611b323bf906d42 GIT binary patch literal 4796 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 literal 0 HcmV?d00001 diff --git a/test/request/circle/normal.slvs b/test/request/circle/normal.slvs new file mode 100644 index 0000000..1ea104f --- /dev/null +++ b/test/request/circle/normal.slvs @@ -0,0 +1,283 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/circle/normal_dof.png b/test/request/circle/normal_dof.png new file mode 100644 index 0000000000000000000000000000000000000000..0e62eba3dbcd8923d76338faf33196ccbd7ecc55 GIT binary patch literal 4830 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 literal 0 HcmV?d00001 diff --git a/test/request/circle/normal_v20.slvs b/test/request/circle/normal_v20.slvs new file mode 100644 index 0000000..cdd2810 --- /dev/null +++ b/test/request/circle/normal_v20.slvs @@ -0,0 +1,283 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/circle/normal_v22.slvs b/test/request/circle/normal_v22.slvs new file mode 100644 index 0000000..d245f28 --- /dev/null +++ b/test/request/circle/normal_v22.slvs @@ -0,0 +1,283 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040040 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=400 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=13000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.distance.v=00040040 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040040 +Entity.type=4000 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actDistance=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/circle/test.cpp b/test/request/circle/test.cpp new file mode 100644 index 0000000..82cd878 --- /dev/null +++ b/test/request/circle/test.cpp @@ -0,0 +1,45 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(free_in_3d_roundtrip) { + CHECK_LOAD("free_in_3d.slvs"); + CHECK_RENDER("free_in_3d.png"); + CHECK_SAVE("free_in_3d.slvs"); +} + +TEST_CASE(free_in_3d_migrate_from_v20) { + CHECK_LOAD("free_in_3d_v20.slvs"); + CHECK_SAVE("free_in_3d.slvs"); +} + +TEST_CASE(free_in_3d_migrate_from_v22) { + CHECK_LOAD("free_in_3d_v22.slvs"); + CHECK_SAVE("free_in_3d.slvs"); +} + +TEST_CASE(normal_dof) { + CHECK_LOAD("normal.slvs"); + SS.GenerateAll(SolveSpaceUI::Generate::ALL, /*andFindFree=*/true); + CHECK_RENDER("normal_dof.png"); +} + +TEST_CASE(free_in_3d_dof) { + CHECK_LOAD("free_in_3d.slvs"); + SS.GenerateAll(SolveSpaceUI::Generate::ALL, /*andFindFree=*/true); + CHECK_RENDER_ISO("free_in_3d_dof.png"); +} diff --git a/test/request/cubic/normal.png b/test/request/cubic/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..e7eb94d81a78bb0ccc1e5238b2b7fe99fb678396 GIT binary patch literal 4949 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 Y1<6ABkAArs{2Kvy?(y4QwTqhlPY<7wBme*a literal 0 HcmV?d00001 diff --git a/test/request/datum_point/normal.slvs b/test/request/datum_point/normal.slvs new file mode 100644 index 0000000..867c76b --- /dev/null +++ b/test/request/datum_point/normal.slvs @@ -0,0 +1,252 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/datum_point/normal_v20.slvs b/test/request/datum_point/normal_v20.slvs new file mode 100644 index 0000000..afec571 --- /dev/null +++ b/test/request/datum_point/normal_v20.slvs @@ -0,0 +1,250 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/datum_point/normal_v22.slvs b/test/request/datum_point/normal_v22.slvs new file mode 100644 index 0000000..50f8877 --- /dev/null +++ b/test/request/datum_point/normal_v22.slvs @@ -0,0 +1,252 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=101 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/datum_point/test.cpp b/test/request/datum_point/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/request/datum_point/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/request/image/drawing.png b/test/request/image/drawing.png new file mode 100644 index 0000000000000000000000000000000000000000..56bedb7523f0a12bb142429e073d77ef8b58f8ac GIT binary patch literal 8565 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 literal 0 HcmV?d00001 diff --git a/test/request/image/linked.slvs b/test/request/image/linked.slvs new file mode 100644 index 0000000..4819058 --- /dev/null +++ b/test/request/image/linked.slvs @@ -0,0 +1,430 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000003 +Group.type=5300 +Group.order=2 +Group.name=normal +Group.activeWorkplane.v=00010000 +Group.color=00646464 +Group.skipFirst=0 +Group.meshCombine=2 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ + 1 00010000 0 + 2 00010001 0 + 3 00010020 0 + 4 00020000 0 + 5 00020001 0 + 6 00020020 0 + 7 00030000 0 + 8 00030001 0 + 9 00030020 0 + 10 00040000 0 + 11 00040001 0 + 12 00040002 0 + 13 00040003 0 + 14 00040004 0 + 15 00040020 0 + 16 80020000 0 + 17 80020001 0 + 18 80020002 0 +} +Group.impFileRel=normal.slvs +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=40000001 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=80030000 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030001 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=80030002 +AddParam + +Param.h.v.=80030003 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=80030004 +AddParam + +Param.h.v.=80030005 +AddParam + +Param.h.v.=80030006 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=0 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030002 +Entity.type=2011 +Entity.construction=1 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030003 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030005 +Entity.type=2011 +Entity.construction=1 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030006 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030005 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030008 +Entity.type=2011 +Entity.construction=1 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030009 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030008 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000a +Entity.type=16000 +Entity.construction=0 +Entity.file=drawing.png +Entity.point[0].v=8003000b +Entity.point[1].v=8003000c +Entity.point[2].v=8003000d +Entity.point[3].v=8003000e +Entity.normal.v=8003000f +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000b +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000c +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000d +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000e +Entity.type=2011 +Entity.construction=0 +Entity.actPoint.x=20.00000000000000000000 +Entity.actPoint.y=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=8003000f +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=8003000b +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030011 +Entity.type=3011 +Entity.construction=0 +Entity.point[0].v=80030012 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80030012 +Entity.type=2011 +Entity.construction=1 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=110 +Constraint.group.v=00000003 +Constraint.workplane.v=00010000 +Constraint.valP.v=40000001 +Constraint.entityA.v=80030006 +Constraint.entityB.v=00020020 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/request/image/normal.slvs b/test/request/image/normal.slvs new file mode 100644 index 0000000..dab1385 --- /dev/null +++ b/test/request/image/normal.slvs @@ -0,0 +1,319 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +Param.val=10.00000000000000000000 +AddParam + +Param.h.v.=00040013 +AddParam + +Param.h.v.=00040014 +AddParam + +Param.h.v.=00040016 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=00040017 +AddParam + +Param.h.v.=00040019 +Param.val=15.00000000000000000000 +AddParam + +Param.h.v.=0004001a +Param.val=10.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=700 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +Request.file=drawing.png +Request.aspectRatio=1.50000000000000000000 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=16000 +Entity.construction=0 +Entity.file=drawing.png +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.point[3].v=00040004 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=15.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040004 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=15.00000000000000000000 +Entity.actPoint.y=10.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/image/test.cpp b/test/request/image/test.cpp new file mode 100644 index 0000000..da0954e --- /dev/null +++ b/test/request/image/test.cpp @@ -0,0 +1,14 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + // Can't render images through cairo for now. + // CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(linked_roundtrip) { + CHECK_LOAD("linked.slvs"); + // CHECK_RENDER("linked.png"); + CHECK_SAVE("linked.slvs"); +} diff --git a/test/request/line_segment/normal.png b/test/request/line_segment/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..a7b1c341a730deb665021f7bc262b0c9ddd0e48d GIT binary patch literal 4246 zcmeHLc~FyA0)K>?$RVtNngZ6!+A5NAugal9ks5Rr3E_@%%N3LW8jggex>^d0ju06m zV^J&+2q*y>Ncd0(umqw4Ndc2^XoExo%7zFS*+j)Dot4s=?H@biADK6k@6CMg{oe0- z-`T(K_te!{rvm`c_1f!x2mlyH`P0%+{KJYQKLWrY*voy_;k3&W{38j2XyenvcXp-N z1s{T(FLsqPs^5o)WWTJk=$?}Ro6<{z;ljs7#{tBxC za{( z<2JCQRf!qmtP6piwbjldyc1&7ta`oPnbH2zr;K6a4ZpWIVv-%x-vU`vCO*An6LIB2 zS6fX$G%)(mZXqWwouCPtv=%3DO)y`{0dx)Z4i+S>Y)9k@0Woc_$4>%N?;iY~CFs>3 z?yDjg0bu)O>xUrD4GV5c1vTHkUw#HDqE{cFeo}jP7SqQVVJkc;Vp3e7Opo}$F4%GD zx$g7jGaOX_(&!@`N}7L!V~OfN!ttlT;Tq7bgP5f0yUesVOk|s|%y6GuE}AUcYIh@f zuqDyAK@yzaT9TDAIpkB=QY5hg!VabI*=q1@k+X!gGLJsj%5guLrOVS&lOI)k-uQ@Z zYqtM)+pia2q-h47(>4Z3T72x^s@@xj(zD{qYlX)R8qXI9%TyZw@@mQA4K3rT;o4SC1#e8*V$4r&T}KofVt^*HD?8Q*1N|t+r#4H)M)Oi( z*14^XW+e68(a_Nh*>kzoo~!D-4K;v8iZKrAJmlL@XqzSVl@At%E<7k!CQyFpf^b# zzuRG-#8)q8Y+*t7IfiK5CK%kbeY>D{uve$po^PdG7+odyZ4o-^TAU9pV>yyw#00i#m8S(aIMu7N`LTwHUq2HWEcn ziw9DU0pi9>Z?hJQuoObKimTVJFsuQYS5Lo;{-Z(ttHOi{#a6`*maBlg@VAsECXv%s zImZ|Y@7u)hBsMl=kb68}iMD=LFjJutz`nbf{L5Cgk7Q7y_y0kL6eh&LCjvm`OJS8? zhpT`yj7SCj5PO$!P(hjhD$wAGc*jt)iH8h0z#d)n=4ETT7*55<@303@!8=7E^)4h1 z`FrzpmoRQ=@25J}q<7i_kjd{{>4SF(he+&f|2F_S``ubhrkfqW9Sze!_th2PD=V3F7r>5s z1XUC}+^onkZjO2AIth^!nhw)M_azt5dtI|o-*>=>&vVT&zC9l3z6H<1`!He>do{+l zWn-*OnLj|5v7q9(k>t;MwgS`6e2Fe%v{cRCEVE53LCTqef$p#?fjK62r#^#0R7(mr z139RC7{k=7SZ%Hff?BVU>`Uew4bcFX024n(Re>RRu^_??OCgu90l8jS%D9gH$0^H|EiDUU-J(hx)4UJH+4%_)4ScuLb<6^L+rsKU@6KpUz* z!T5d(2mdsBI~ZcD-Hn04r!^9LCdCCHi%zI>#o7-EM%VK#y$QUZ=bx8u;w<#j4-v-F zw>7oo0h}m^MCTD2rUi4U=KQ%pKc6k8XI*KKMcXQBeluPrbIVQC&KIwqXaZmR<5*^1 znRCw%yn~yxuv=fZYo(!k@S_OVXS4XtOlhs`X!K+}Yu;kMJMbSRXBiDMvS`YD`^?x0 zvy8Y&mrw$8QkKx48Oi5V-;$jq)YUs_4tX{7AjEUeX~Ox$nRJ0|teG^;ETh{qKY}C5 zNM=R3&Ri13aCp|WlBg_NdPF{r&^Y?E13y%-G+!PE7xy^L~0-Mjdb5P{^B57halEh?`WP#H*lGqx-sbF^MfwfnhOIog} z3>miRQf28;;m+q6oM%rirIw4nY|v7P#^nX(aOTDw22vM1UXIm=jL0v9aA$uCpvi0o z>?R(BWr@X8g6SFR-D!Qd{I*!>bwSbX(X~@kkG6;CwU{9E+AZ4C8?>5|Q5-<@8E1OD z+`N}%!yPo*;YlsoKIwO~YmQrTQhpa7_KT=pao2q<;_3#D%QnNiV+$L^V94gpC5`u6 i6IfA{|1T%KR_z%1g3@)6SE2ac0K7c*yI1eVX8#AbP?XXD literal 0 HcmV?d00001 diff --git a/test/request/line_segment/normal.slvs b/test/request/line_segment/normal.slvs new file mode 100644 index 0000000..4914fef --- /dev/null +++ b/test/request/line_segment/normal.slvs @@ -0,0 +1,278 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/line_segment/normal_v20.slvs b/test/request/line_segment/normal_v20.slvs new file mode 100644 index 0000000..fb26b9f --- /dev/null +++ b/test/request/line_segment/normal_v20.slvs @@ -0,0 +1,278 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/line_segment/normal_v22.slvs b/test/request/line_segment/normal_v22.slvs new file mode 100644 index 0000000..167a2c2 --- /dev/null +++ b/test/request/line_segment/normal_v22.slvs @@ -0,0 +1,278 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=200 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=11000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/line_segment/test.cpp b/test/request/line_segment/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/request/line_segment/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/request/ttf_text/normal.png b/test/request/ttf_text/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..b023b8388bcb35bb4aed0b2fe7f0e9f88dc24cc0 GIT binary patch literal 6158 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 B@znqT literal 0 HcmV?d00001 diff --git a/test/request/ttf_text/normal.slvs b/test/request/ttf_text/normal.slvs new file mode 100644 index 0000000..229c63b --- /dev/null +++ b/test/request/ttf_text/normal.slvs @@ -0,0 +1,329 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040016 +Param.val=23.92131768269594616072 +AddParam + +Param.h.v.=00040017 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040019 +Param.val=23.92131768269594616072 +AddParam + +Param.h.v.=0004001a +Param.val=5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=600 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +Request.str=Text +Request.font=Gentium-R.ttf +Request.aspectRatio=2.89213176826959461607 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=15000 +Entity.construction=0 +Entity.str=Text +Entity.font=Gentium-R.ttf +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.point[2].v=00040003 +Entity.point[3].v=00040004 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040003 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=23.92131768269594616072 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040004 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=23.92131768269594616072 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/ttf_text/normal_v20.slvs b/test/request/ttf_text/normal_v20.slvs new file mode 100644 index 0000000..65bc54a --- /dev/null +++ b/test/request/ttf_text/normal_v20.slvs @@ -0,0 +1,290 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=600 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +Request.str=Text +Request.font=Gentium-R.ttf +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=15000 +Entity.construction=0 +Entity.str=Text +Entity.font=GOST.ttf +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/ttf_text/normal_v22.slvs b/test/request/ttf_text/normal_v22.slvs new file mode 100644 index 0000000..3022dcf --- /dev/null +++ b/test/request/ttf_text/normal_v22.slvs @@ -0,0 +1,292 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.activeWorkplane.v=80020000 +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040011 +Param.val=5.00000000000000000000 +AddParam + +Param.h.v.=00040013 +Param.val=-5.00000000000000000000 +AddParam + +Param.h.v.=00040014 +Param.val=-5.00000000000000000000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=600 +Request.workplane.v=80020000 +Request.group.v=00000002 +Request.construction=0 +Request.str=Text +Request.font=Gentium-R.ttf +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=15000 +Entity.construction=0 +Entity.str=Text +Entity.font=Gentium-R.ttf +Entity.point[0].v=00040001 +Entity.point[1].v=00040002 +Entity.normal.v=00040020 +Entity.workplane.v=80020000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040002 +Entity.type=2001 +Entity.construction=0 +Entity.workplane.v=80020000 +Entity.actPoint.x=-5.00000000000000000000 +Entity.actPoint.y=-5.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3001 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.workplane.v=80020000 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + diff --git a/test/request/ttf_text/test.cpp b/test/request/ttf_text/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/request/ttf_text/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/test/request/workplane/normal.png b/test/request/workplane/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..30862b3ff1cd95fb5a817251fb9413f14d037231 GIT binary patch literal 5612 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 literal 0 HcmV?d00001 diff --git a/test/request/workplane/normal.slvs b/test/request/workplane/normal.slvs new file mode 100644 index 0000000..de433f9 --- /dev/null +++ b/test/request/workplane/normal.slvs @@ -0,0 +1,293 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040020 +Param.val=0.88047623921714945894 +AddParam + +Param.h.v.=00040021 +Param.val=-0.27984814233312144127 +AddParam + +Param.h.v.=00040022 +Param.val=-0.36470519963100095362 +AddParam + +Param.h.v.=00040023 +Param.val=-0.11591689595929521861 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=100 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.actNormal.w=0.88047623921714945894 +Entity.actNormal.vx=-0.27984814233312144127 +Entity.actNormal.vy=-0.36470519963100095362 +Entity.actNormal.vz=-0.11591689595929521861 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=1 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/request/workplane/normal_v20.slvs b/test/request/workplane/normal_v20.slvs new file mode 100644 index 0000000..f239329 --- /dev/null +++ b/test/request/workplane/normal_v20.slvs @@ -0,0 +1,291 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allDimsReference=0 +Group.remap={ +} +Group.impFile= +Group.impFileRel= +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040020 +Param.val=0.88047623921714946000 +AddParam + +Param.h.v.=00040021 +Param.val=-0.27984814233312144000 +AddParam + +Param.h.v.=00040022 +Param.val=-0.36470519963100095000 +AddParam + +Param.h.v.=00040023 +Param.val=-0.11591689595929522000 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=100 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.actNormal.w=0.88047623921714946000 +Entity.actNormal.vx=-0.27984814233312144000 +Entity.actNormal.vy=-0.36470519963100095000 +Entity.actNormal.vz=-0.11591689595929522000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/request/workplane/normal_v22.slvs b/test/request/workplane/normal_v22.slvs new file mode 100644 index 0000000..d79e899 --- /dev/null +++ b/test/request/workplane/normal_v22.slvs @@ -0,0 +1,293 @@ +±²³SolveSpaceREVa + + +Group.h.v=00000001 +Group.type=5000 +Group.name=#references +Group.color=ff000000 +Group.skipFirst=0 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Group.h.v=00000002 +Group.type=5001 +Group.order=1 +Group.name=sketch-in-plane +Group.color=ff000000 +Group.subtype=6000 +Group.skipFirst=0 +Group.predef.q.w=1.00000000000000000000 +Group.predef.origin.v=00010001 +Group.predef.swapUV=0 +Group.predef.negateU=0 +Group.predef.negateV=0 +Group.visible=1 +Group.suppress=0 +Group.relaxConstraints=0 +Group.allowRedundant=0 +Group.allDimsReference=0 +Group.scale=1.00000000000000000000 +Group.remap={ +} +AddGroup + +Param.h.v.=00010010 +AddParam + +Param.h.v.=00010011 +AddParam + +Param.h.v.=00010012 +AddParam + +Param.h.v.=00010020 +Param.val=1.00000000000000000000 +AddParam + +Param.h.v.=00010021 +AddParam + +Param.h.v.=00010022 +AddParam + +Param.h.v.=00010023 +AddParam + +Param.h.v.=00020010 +AddParam + +Param.h.v.=00020011 +AddParam + +Param.h.v.=00020012 +AddParam + +Param.h.v.=00020020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020021 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020022 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00020023 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030010 +AddParam + +Param.h.v.=00030011 +AddParam + +Param.h.v.=00030012 +AddParam + +Param.h.v.=00030020 +Param.val=0.50000000000000000000 +AddParam + +Param.h.v.=00030021 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030022 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00030023 +Param.val=-0.50000000000000000000 +AddParam + +Param.h.v.=00040010 +AddParam + +Param.h.v.=00040011 +AddParam + +Param.h.v.=00040012 +AddParam + +Param.h.v.=00040020 +Param.val=0.88047623921714945894 +AddParam + +Param.h.v.=00040021 +Param.val=-0.27984814233312144127 +AddParam + +Param.h.v.=00040022 +Param.val=-0.36470519963100095362 +AddParam + +Param.h.v.=00040023 +Param.val=-0.11591689595929521861 +AddParam + +Request.h.v=00000001 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000002 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000003 +Request.type=100 +Request.group.v=00000001 +Request.construction=0 +AddRequest + +Request.h.v=00000004 +Request.type=100 +Request.group.v=00000002 +Request.construction=0 +AddRequest + +Entity.h.v=00010000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.normal.v=00010020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00010020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00010001 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.normal.v=00020020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00020020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00020001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=0.50000000000000000000 +Entity.actNormal.vy=0.50000000000000000000 +Entity.actNormal.vz=0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.normal.v=00030020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00030020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00030001 +Entity.actNormal.w=0.50000000000000000000 +Entity.actNormal.vx=-0.50000000000000000000 +Entity.actNormal.vy=-0.50000000000000000000 +Entity.actNormal.vz=-0.50000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.normal.v=00040020 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040001 +Entity.type=2000 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Entity.h.v=00040020 +Entity.type=3000 +Entity.construction=0 +Entity.point[0].v=00040001 +Entity.actNormal.w=0.88047623921714945894 +Entity.actNormal.vx=-0.27984814233312144127 +Entity.actNormal.vy=-0.36470519963100095362 +Entity.actNormal.vz=-0.11591689595929521861 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020000 +Entity.type=10000 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.normal.v=80020001 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020001 +Entity.type=3010 +Entity.construction=0 +Entity.point[0].v=80020002 +Entity.actNormal.w=1.00000000000000000000 +Entity.actVisible=1 +AddEntity + +Entity.h.v=80020002 +Entity.type=2012 +Entity.construction=0 +Entity.actVisible=1 +AddEntity + +Constraint.h.v=00000001 +Constraint.type=20 +Constraint.group.v=00000002 +Constraint.ptA.v=00010001 +Constraint.ptB.v=00040001 +Constraint.other=0 +Constraint.other2=0 +Constraint.reference=0 +AddConstraint + diff --git a/test/request/workplane/test.cpp b/test/request/workplane/test.cpp new file mode 100644 index 0000000..5e9db1b --- /dev/null +++ b/test/request/workplane/test.cpp @@ -0,0 +1,17 @@ +#include "harness.h" + +TEST_CASE(normal_roundtrip) { + CHECK_LOAD("normal.slvs"); + CHECK_RENDER("normal.png"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v20) { + CHECK_LOAD("normal_v20.slvs"); + CHECK_SAVE("normal.slvs"); +} + +TEST_CASE(normal_migrate_from_v22) { + CHECK_LOAD("normal_v22.slvs"); + CHECK_SAVE("normal.slvs"); +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt deleted file mode 100644 index 93fb750..0000000 --- a/tools/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -include_directories( - ${PNG_INCLUDE_DIRS}) - -link_directories( - ${PNG_LIBRARY_DIRS}) - -add_executable(png2c - png2c.cpp) - -target_link_libraries(png2c - ${PNG_LIBRARIES}) - -add_executable(unifont2c - unifont2c.cpp) - -target_link_libraries(unifont2c - ${PNG_LIBRARIES} ${ZLIB_LIBRARIES}) - -add_executable(lff2c - lff2c.cpp) - -target_link_libraries(lff2c - ${ZLIB_LIBRARIES}) diff --git a/tools/lff2c.cpp b/tools/lff2c.cpp deleted file mode 100644 index 098fd0a..0000000 --- a/tools/lff2c.cpp +++ /dev/null @@ -1,410 +0,0 @@ -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TOLERANCE 1e-6 - -double correctAngle(double a) { - return M_PI + remainder(a - M_PI, 2 * M_PI); -} - -struct Point { - double x; - double y; - - Point operator+(const Point &o) const { return { x + o.x, y + o.y }; } - Point operator-(const Point &o) const { return { x - o.x, y - o.y }; } - Point operator*(const Point &o) const { return { x * o.x, y * o.y }; } - Point operator/(const Point &o) const { return { x / o.x, y / o.y }; } - - Point operator*(double k) const { return { x * k, y * k }; } - Point operator/(double k) const { return { x / k, y / k }; } - - double length() const{ - return sqrt(x * x + y * y); - } - - double angle() const { - return correctAngle(atan2(y, x)); - } - - double distanceTo(const Point &v) const { - return (*this - v).length(); - } - - double angleTo(const Point &v) const { - return (v - *this).angle(); - } - - static Point polar(double radius, double angle) { - return { radius * cos(angle), radius * sin(angle) }; - } - - bool operator==(const Point &o) const { return x == o.x && y == o.y; } - bool operator!=(const Point &o) const { return x != o.x || y != o.y; } - -}; - -struct Curve { - std::vector points; -}; - -struct Glyph { - char32_t character; - char32_t baseCharacter; - std::vector curves; - - void getHorizontalBounds(double *rminx, double *rmaxx) const { - double minx = 0; - double maxx = 0; - if(!curves.empty()) { - minx = curves[0].points[0].x; - maxx = minx; - for(const Curve &c : curves) { - for(const Point &p : c.points) { - maxx = std::max(maxx, p.x); - minx = std::min(minx, p.x); - } - } - } - if(rminx) *rminx = minx; - if(rmaxx) *rmaxx = maxx; - } - - void getVerticalBounds(double *rminy, double *rmaxy) const { - double miny = 0; - double maxy = 0; - if(!curves.empty()) { - miny = curves[0].points[0].y; - maxy = miny; - for(const Curve &c : curves) { - for(const Point &p : c.points) { - maxy = std::max(maxy, p.y); - miny = std::min(miny, p.y); - } - } - } - if(rminy) *rminy = miny; - if(rmaxy) *rmaxy = maxy; - } - - void getHorizontalMetrics(double *leftSideBearing, double *boundingWidth) const { - double minx, maxx; - getHorizontalBounds(&minx, &maxx); - *leftSideBearing = minx; - *boundingWidth = maxx - minx; - } - - bool operator<(const Glyph &o) const { return character < o.character; } -}; - -struct Font { - double letterSpacing; - double wordSpacing; - std::vector glyphs; - - const Glyph &findGlyph(char32_t character) { - return *std::find_if(glyphs.begin(), glyphs.end(), - [&](const Glyph &g) { return g.character == character; }); - } - - void getGlyphBound(double *rminw, double *rminh, double *rmaxw, double *rmaxh) { - if(glyphs.empty()) { - *rminw = 0.0; - *rmaxw = 0.0; - *rminh = 0.0; - *rmaxh = 0.0; - return; - } - - glyphs[0].getHorizontalBounds(rminw, rmaxw); - glyphs[0].getVerticalBounds(rminh, rmaxh); - for(const Glyph &g : glyphs) { - double minw, minh, maxw, maxh; - g.getHorizontalBounds(&minw, &maxw); - g.getVerticalBounds(&minh, &maxh); - *rminw = std::min(*rminw, minw); - *rminh = std::min(*rminh, minh); - *rmaxw = std::max(*rmaxw, maxw); - *rmaxh = std::max(*rmaxh, maxh); - } - } - - void createArc(Curve &curve, const Point &cp, double radius, - double a1, double a2, bool reversed) { - if (radius < 1e-6) return; - - double aSign = 1.0; - if(reversed) { - if(a1 <= a2 + TOLERANCE) a1 += 2.0 * M_PI; - aSign = -1.0; - } else { - if(a2 <= a1 + TOLERANCE) a2 += 2.0 * M_PI; - } - - // Angle Step (rad) - double da = fabs(a2 - a1); - int numPoints = 8; - double aStep = aSign * da / double(numPoints); - - for(int i = 0; i <= numPoints; i++) { - curve.points.push_back(cp + Point::polar(radius, a1 + aStep * i)); - } - } - - void createBulge(const Point &v, double bulge, Curve &curve) { - bool reversed = bulge < 0.0; - double alpha = atan(bulge) * 4.0; - Point &point = curve.points.back(); - - Point middle = (point + v) / 2.0; - double dist = point.distanceTo(v) / 2.0; - double angle = point.angleTo(v); - - // alpha can't be 0.0 at this point - double radius = fabs(dist / sin(alpha / 2.0)); - double wu = fabs(radius*radius - dist*dist); - double h = sqrt(wu); - - if(bulge > 0.0) { - angle += M_PI_2; - } else { - angle -= M_PI_2; - } - - if (fabs(alpha) > M_PI) { - h *= -1.0; - } - - Point center = Point::polar(h, angle); - center = center + middle; - - double a1 = center.angleTo(point); - double a2 = center.angleTo(v); - createArc(curve, center, radius, a1, a2, reversed); - } - - void readLff(const std::string &path) { - gzFile lfffont = gzopen(path.c_str(), "rb"); - if(!lfffont) { - std::cerr << path << ": gzopen failed" << std::endl; - std::exit(1); - } - - // Read line by line until we find a new letter: - Glyph *currentGlyph = nullptr; - while(!gzeof(lfffont)) { - std::string line; - do { - char buf[128] = {0}; - if(!gzgets(lfffont, buf, sizeof(buf))) - break; - line += buf; - } while(line.back() != '\n'); - - if(line.empty() || line[0] == '\n') { - continue; - } else if(line[0] == '#') { - // This is comment or metadata. - std::istringstream ss(line.substr(1)); - - std::vector tokens; - while(!ss.eof()) { - std::string token; - std::getline(ss, token, ':'); - std::istringstream(token) >> token; // trim - if(!token.empty()) - tokens.push_back(token); - } - - // If not in form of "a:b" then it's not metadata, just a comment. - if (tokens.size() != 2) - continue; - - std::string &identifier = tokens[0]; - std::string &value = tokens[1]; - - std::transform(identifier.begin(), identifier.end(), identifier.begin(), - ::tolower); - if (identifier == "letterspacing") { - std::istringstream(value) >> letterSpacing; - } else if (identifier == "wordspacing") { - std::istringstream(value) >> wordSpacing; - } else if (identifier == "linespacingfactor") { - /* don't care */ - } else if (identifier == "author") { - /* don't care */ - } else if (identifier == "name") { - /* don't care */ - } else if (identifier == "license") { - /* don't care */ - } else if (identifier == "encoding") { - /* don't care */ - } else if (identifier == "created") { - /* don't care */ - } - } else if(line[0] == '[') { - // This is a glyph. - size_t closingPos; - char32_t chr = std::stoi(line.substr(1), &closingPos, 16);; - if(line[closingPos + 1] != ']') { - std::cerr << "unrecognized character number: " << line << std::endl; - currentGlyph = nullptr; - continue; - } - - glyphs.emplace_back(); - currentGlyph = &glyphs.back(); - currentGlyph->character = chr; - currentGlyph->baseCharacter = 0; - } else if(currentGlyph != nullptr) { - if (line[0] == 'C') { - // This is a reference to another glyph. - currentGlyph->baseCharacter = std::stoi(line.substr(1), nullptr, 16); - } else { - // This is a series of curves. - currentGlyph->curves.emplace_back(); - Curve &curve = currentGlyph->curves.back(); - - std::stringstream ss(line); - while (!ss.eof()) { - std::string vertex; - std::getline(ss, vertex, ';'); - - std::stringstream ssv(vertex); - std::string coord; - Point p; - - if(!std::getline(ssv, coord, ',')) continue; - p.x = std::stod(coord); - - if(!std::getline(ssv, coord, ',')) continue; - p.y = std::stod(coord); - - if(!std::getline(ssv, coord, ',') || coord[0] != 'A') { - // This is just a point. - curve.points.push_back(p); - } else { - // This is a point with a bulge. - double bulge = std::stod(coord.substr(1)); - createBulge(p, bulge, curve); - } - } - } - } else { - std::cerr << "unrecognized line: " << line << std::endl; - } - } - gzclose(lfffont); - } - - void writeCppHeader(const std::string &hName) { - std::sort(glyphs.begin(), glyphs.end()); - - std::ofstream ts(hName, std::ios::out); - - double minX, minY, maxX, maxY; - getGlyphBound(&minX, &minY, &maxX, &maxY); - - double size = 32766.0; - double scale = size / std::max({ fabs(maxX), fabs(minX), fabs(maxY), fabs(minY) }); - - double capHeight, ascender, descender; - findGlyph('A').getVerticalBounds(nullptr, &capHeight); - findGlyph('h').getVerticalBounds(nullptr, &ascender); - findGlyph('p').getVerticalBounds(&descender, nullptr); - - // We use tabs for indentation here to make compilation slightly faster - ts << - "/**** This is a generated file - do not edit ****/\n\n" - "#ifndef __VECTORFONT_TABLE_H\n" - "#define __VECTORFONT_TABLE_H\n" - "\n" - "#define PEN_UP 32767\n" - "#define UP PEN_UP\n" - "\n" - "#define FONT_CAP_HEIGHT ((int16_t)" << (int)floor(capHeight * scale) << ")\n" << - "#define FONT_ASCENDER ((int16_t)" << (int)floor(ascender * scale) << ")\n" << - "#define FONT_DESCENDER ((int16_t)" << (int)floor(descender * scale) << ")\n" << - "#define FONT_SIZE (FONT_ASCENDER-FONT_DESCENDER)\n" - "\n" - "struct VectorGlyph {\n" - "\tchar32_t character;\n" - "\tchar32_t baseCharacter;\n" - "\tint leftSideBearing;\n" - "\tint boundingWidth;\n" - "\tint advanceWidth;\n" - "\tconst int16_t *data;\n" - "};\n" - "\n" - "const int16_t VectorFontData[] = {\n" - "\tUP, UP,\n"; - - std::map glyphIndexes; - size_t index = 2; - for(const Glyph &g : glyphs) { - ts << "\t// U+" << std::hex << g.character << std::dec << "\n"; - glyphIndexes[g.character] = index; - for(const Curve &c : g.curves) { - for(const Point &p : c.points) { - ts << "\t" << (int)floor(p.x * scale) << ", " << - (int)floor(p.y * scale) << ",\n"; - index += 2; - } - ts << "\tUP, UP,\n"; - index += 2; - } - ts << "\tUP, UP,\n"; // end-of-glyph marker - index += 2; - } - - ts << - "};\n" - "\n" - "const VectorGlyph VectorFont[] = {\n" - "\t// U+20\n" - "\t{ 32, 0, 0, 0, " << (int)floor(wordSpacing * scale) << ", &VectorFontData[0] },\n"; - - for(const Glyph &g : glyphs) { - double leftSideBearing, boundingWidth; - g.getHorizontalMetrics(&leftSideBearing, &boundingWidth); - - ts << "\t// U+" << std::hex << g.character << std::dec << "\n"; - ts << "\t{ " << g.character << ", " - << g.baseCharacter << ", " - << (int)floor(leftSideBearing * scale) << ", " - << (int)floor(boundingWidth * scale) << ", " - << (int)floor((leftSideBearing + boundingWidth + - letterSpacing) * scale) << ", "; - ts << "&VectorFontData[" << glyphIndexes[g.character] << "] },\n"; - } - - ts << - "};\n" - "\n" - "#undef UP\n" - "\n" - "#endif\n"; - } -}; - -int main(int argc, char** argv) { - if(argc != 3) { - std::cerr << "Usage: " << argv[0] << "

\n" << std::endl; - return 1; - } - - Font font; - font.readLff(argv[2]); - font.writeCppHeader(argv[1]); - - return 0; -} diff --git a/tools/png2c.cpp b/tools/png2c.cpp deleted file mode 100644 index 49d2249..0000000 --- a/tools/png2c.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include -#include -#include -#include -#include - -#define die(msg) do { fprintf(stderr, "%s\n", msg); abort(); } while(0) - -void write_png(FILE *out, const char *filename) { - FILE *fp = fopen(filename, "rb"); - if (!fp) - die("png fopen failed"); - - png_byte header[8] = {}; - if(fread(header, 1, 8, fp) != 8) - die("png fread failed"); - - if(png_sig_cmp(header, 0, 8)) - die("png_sig_cmp failed"); - - png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if(!png) - die("png_create_read_struct failed"); - - png_set_expand(png); - png_set_strip_alpha(png); - - png_infop png_info = png_create_info_struct(png); - if (!png_info) - die("png_create_info_struct failed"); - - if (setjmp(png_jmpbuf(png))) - die("png_init_io failed"); - - png_init_io(png, fp); - png_set_sig_bytes(png, 8); - - png_read_info(png, png_info); - - int width = png_get_image_width(png, png_info); - int height = png_get_image_height(png, png_info); - if(width != 24 || height != 24) - die("not a 24x24 png"); - - png_read_update_info(png, png_info); - - if (setjmp(png_jmpbuf(png))) - die("png_read_image failed"); - - png_bytepp image = (png_bytepp) malloc(sizeof(png_bytep) * height); - for (int y = 0; y < height; y++) - image[y] = (png_bytep) malloc(png_get_rowbytes(png, png_info)); - - png_read_image(png, (png_bytepp) image); - - for(int y = height - 1; y >= 0; y--) { - for(int x = 0; x < (int)png_get_rowbytes(png, png_info); x += 3) { - unsigned char r = image[y][x + 0], - g = image[y][x + 1], - b = image[y][x + 2]; - - if(r + g + b < 11) - r = g = b = 30; - fprintf(out, " 0x%02x, 0x%02x, 0x%02x,\n", r, g, b); - } - } - - for (int y = 0; y < height; y++) - free(image[y]); - free(image); - - fclose(fp); - - png_destroy_read_struct(&png, &png_info, NULL); -} - -int main(int argc, char** argv) { - if(argc < 3) { - fprintf(stderr, "Usage: %s
...\n", - argv[0]); - return 1; - } - - FILE *source = fopen(argv[1], "wt"); - if(!source) - die("source fopen failed"); - - FILE *header = fopen(argv[2], "wt"); - if(!header) - die("header fopen failed"); - - fprintf(source, "/**** This is a generated file - do not edit ****/\n\n"); - fprintf(header, "/**** This is a generated file - do not edit ****/\n\n"); - - for(int i = 3; i < argc; i++) { - const char *filename = argv[i]; - const char *basename = strrchr(filename, '/'); /* cmake uses / even on Windows */ - if(basename == NULL) { - basename = filename; - } else { - basename++; // skip separator - } - - char *stemname = (char*) calloc(strlen(basename), 1); - strncpy(stemname, basename, strchr(basename, '.') - basename); - for(size_t j = 0; j < strlen(stemname); j++) { - if(!isalnum(stemname[j])) - stemname[j] = '_'; - } - - fprintf(header, "extern unsigned char Icon_%s[24*24*3];\n", stemname); - - fprintf(source, "unsigned char Icon_%s[24*24*3] = {\n", stemname); - write_png(source, filename); - fprintf(source, "};\n\n"); - - free(stemname); - } - - fclose(source); - fclose(header); -} diff --git a/tools/unifont2c.cpp b/tools/unifont2c.cpp deleted file mode 100644 index 7e9bf7a..0000000 --- a/tools/unifont2c.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#include -#include -#include -#include -#include - -#define die(msg) do { fprintf(stderr, "%s\n", msg); abort(); } while(0) - -#ifdef NDEBUG -#define COMPRESSION_LEVEL 9 -#else -#define COMPRESSION_LEVEL 5 -#endif - -unsigned short* read_png(const char *filename) { - FILE *fp = fopen(filename, "rb"); - if (!fp) - die("png fopen failed"); - - png_byte header[8] = {}; - if(fread(header, 1, 8, fp) != 8) - die("png fread failed"); - - if(png_sig_cmp(header, 0, 8)) - die("png_sig_cmp failed"); - - png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if(!png) - die("png_create_read_struct failed"); - - png_set_expand(png); - png_set_strip_alpha(png); - - png_infop png_info = png_create_info_struct(png); - if (!png_info) - die("png_create_info_struct failed"); - - if (setjmp(png_jmpbuf(png))) - die("png_init_io failed"); - - png_init_io(png, fp); - png_set_sig_bytes(png, 8); - - png_read_info(png, png_info); - - int width = png_get_image_width(png, png_info); - int height = png_get_image_height(png, png_info); - if(width != 16 || height != 16) - die("not a 16x16 png"); - - png_read_update_info(png, png_info); - - if (setjmp(png_jmpbuf(png))) - die("png_read_image failed"); - - png_bytepp image = (png_bytepp) malloc(sizeof(png_bytep) * height); - for (int y = 0; y < height; y++) - image[y] = (png_bytep) malloc(png_get_rowbytes(png, png_info)); - - png_read_image(png, (png_bytepp) image); - - unsigned short *glyph = (unsigned short *) calloc(16, 2); - - for(int y = 0; y < height; y++) { - for(int x = 0; x < (int)png_get_rowbytes(png, png_info); x += 3) { - unsigned char r = image[y][x + 0], - g = image[y][x + 1], - b = image[y][x + 2]; - - if(r + g + b >= 11) { - glyph[y] |= 1 << (width - x / 3); - } - } - } - - for (int y = 0; y < height; y++) - free(image[y]); - free(image); - - fclose(fp); - - png_destroy_read_struct(&png, &png_info, NULL); - - return glyph; -} - -const static unsigned short replacement[16] = { - 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, - 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, - 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, - 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, -}; - -struct CodepointProperties { - bool exists:1; - bool isWide:1; -}; - -int main(int argc, char** argv) { - if(argc < 3) { - fprintf(stderr, "Usage: %s
...\n" - " where s are mapped into private use area\n" - " starting at U+E000.\n", - argv[0]); - return 1; - } - - const int codepoint_count = 0x10000; - unsigned short **font = - (unsigned short **)calloc(sizeof(unsigned short*), codepoint_count); - CodepointProperties *properties = - (CodepointProperties *)calloc(sizeof(CodepointProperties), codepoint_count); - - const int private_start = 0xE000; - for(int i = 3; i < argc; i++) { - int codepoint = private_start + i - 3; - font[codepoint] = read_png(argv[i]); - properties[codepoint].exists = true; - } - - gzFile unifont = gzopen(argv[2], "rb"); - if(!unifont) - die("unifont fopen failed"); - - while(1) { - char buf[100]; - if(!gzgets(unifont, buf, sizeof(buf))){ - if(gzeof(unifont)) { - break; - } else { - die("unifont gzgets failed"); - } - } - - unsigned short codepoint; - unsigned short *glyph = (unsigned short *) calloc(32, 1); - bool isWide; - if( sscanf(buf, "%4hx:%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx" - "%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx\n", - &codepoint, - &glyph[0], &glyph[1], &glyph[2], &glyph[3], - &glyph[4], &glyph[5], &glyph[6], &glyph[7], - &glyph[8], &glyph[9], &glyph[10], &glyph[11], - &glyph[12], &glyph[13], &glyph[14], &glyph[15]) == 17) { - /* read 16x16 character */ - isWide = true; - } else if(sscanf(buf, "%4hx:%2hx%2hx%2hx%2hx%2hx%2hx%2hx%2hx" - "%2hx%2hx%2hx%2hx%2hx%2hx%2hx%2hx\n", - &codepoint, - &glyph[0], &glyph[1], &glyph[2], &glyph[3], - &glyph[4], &glyph[5], &glyph[6], &glyph[7], - &glyph[8], &glyph[9], &glyph[10], &glyph[11], - &glyph[12], &glyph[13], &glyph[14], &glyph[15]) == 17) { - /* read 8x16 character */ - for(int i = 0; i < 16; i++) - glyph[i] <<= 8; - isWide = false; - } else { - die("parse unifont character"); - } - - font[codepoint] = glyph; - properties[codepoint].exists = true; - properties[codepoint].isWide = isWide; - } - - gzclose(unifont); - - FILE *source = fopen(argv[1], "wt"); - if(!source) - die("source fopen failed"); - - const int chunk_size = 64 * 64, - chunks = codepoint_count / chunk_size; - - const int chunk_input_size = chunk_size * 16 * 16; - unsigned int chunk_output_size[chunks] = {}; - - unsigned char *chunk_data = (unsigned char *)calloc(1, chunk_input_size); - unsigned int chunk_data_index; - - fprintf(source, "/**** This is a generated file - do not edit ****/\n\n"); - - for(int chunk_index = 0; chunk_index < chunks; chunk_index++) { - chunk_data_index = 0; - - const int chunk_start = chunk_index * chunk_size; - for(int codepoint = chunk_start; codepoint < chunk_start + chunk_size; codepoint++) { - const unsigned short *glyph = font[codepoint] != NULL ? font[codepoint] : replacement; - for(int x = 15; x >= 0; x--) { - for(int y = 0; y < 16; y++) { - chunk_data[chunk_data_index++] = (glyph[y] & (1 << x)) ? 0xff : 0; - } - } - - if(font[codepoint] != NULL) - free(font[codepoint]); - } - - fprintf(source, "static const uint8_t CompressedFontTextureChunk%d[] = {\n", - chunk_start / chunk_size); - - z_stream stream; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - stream.opaque = Z_NULL; - if(deflateInit(&stream, COMPRESSION_LEVEL) != Z_OK) - die("deflateInit failed"); - - stream.next_in = chunk_data; - stream.avail_in = chunk_input_size; - - do { - unsigned char compressed_chunk_data[16384] = {}; - stream.next_out = compressed_chunk_data; - stream.avail_out = sizeof(compressed_chunk_data); - deflate(&stream, Z_FINISH); - - chunk_output_size[chunk_index] += sizeof(compressed_chunk_data) - stream.avail_out; - for(size_t i = 0; i < sizeof(compressed_chunk_data) - stream.avail_out; i += 16) { - unsigned char *d = &compressed_chunk_data[i]; - fprintf(source, " %3d, %3d, %3d, %3d, %3d, %3d, %3d, %3d, " - "%3d, %3d, %3d, %3d, %3d, %3d, %3d, %3d,\n", - d[ 0], d[ 1], d[ 2], d[ 3], d[ 4], d[ 5], d[ 6], d[ 7], - d[ 8], d[ 9], d[10], d[11], d[12], d[13], d[14], d[15]); - } - } while(stream.avail_out == 0); - - deflateEnd(&stream); - - fprintf(source, "};\n\n"); - } - - free(chunk_data); - free(font); - - fprintf(source, "static const struct {\n" - " size_t length;" - " const uint8_t *data;" - "} CompressedFontTexture[%d] = {\n", chunks); - for(int i = 0; i < chunks; i++) { - fprintf(source, " { %d, CompressedFontTextureChunk%d },\n", - chunk_output_size[i], i); - } - fprintf(source, "};\n\n"); - - fprintf(source, "struct GlyphProperties {\n" - " bool exists:1;\n" - " bool isWide:1;\n" - "} CodepointProperties[%d] = {\n", codepoint_count); - for(int i = 0; i < codepoint_count; i++) { - fprintf(source, " { %s, %s },\n", - properties[i].exists ? "true" : "false", - properties[i].isWide ? "true" : "false"); - } - fprintf(source, "};\n"); - - free(properties); - - fclose(source); -} -- 2.30.2

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 Vgne?E_}d^?F4166|?_}*v#6rh# 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 diff --git a/src/icons/mesh.png b/src/icons/mesh.png deleted file mode 100644 index dde867b613bb9a50e03e0ca387e6f8816ca158c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28445 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 z 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 Eg1f7$-~a#s diff --git a/src/icons/parallel.png b/src/icons/parallel.png deleted file mode 100644 index 4b8ef991462f9b565a6327af11c16f8cde7b2ec2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26455 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�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

ZPK)~r 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